/**
 * LIN File Parser & Complex Linetype Registry
 * Parses AutoCAD LIN file definitions (including text and shape descriptors).
 */

import { ICAD_LIN, KOSDIC_LIN } from './linData';

export interface LinElement {
  type: 'dash' | 'gap' | 'shape' | 'text';
  val?: number;            // dash length (+) or gap length (-)
  shapeName?: string;      // e.g. KSC35, CIRC1, BOX, TRACK1, BAT, ZIG
  shxFile?: string;        // e.g. kosdic.shx, ltypeshp.shx
  text?: string;           // e.g. "L2", "GAS", "HW"
  style?: string;          // e.g. Standard
  scale: number;           // s=... (default 1.0)
  rotation: number;        // r=... (angle in degrees)
  isAbsoluteAngle: boolean;// a=...
  isUprightAngle: boolean; // u=...
  xOffset: number;         // x=...
  yOffset: number;         // y=...
}

export interface LinPattern {
  name: string;
  description: string;
  elements: LinElement[];
  patternLength: number;   // total length sum of dashes + abs(gaps)
}

function parseBracketElement(inner: string): LinElement {
  const parts: string[] = [];
  let cur = '';
  let inQuote = false;
  for (let i = 0; i < inner.length; i++) {
    const c = inner[i];
    if (c === '"') inQuote = !inQuote;
    if (c === ',' && !inQuote) {
      parts.push(cur.trim());
      cur = '';
    } else {
      cur += c;
    }
  }
  if (cur.trim()) parts.push(cur.trim());

  let type: 'shape' | 'text' = 'shape';
  let shapeName: string | undefined;
  let shxFile: string | undefined;
  let text: string | undefined;
  let style: string | undefined;

  let scale = 1.0;
  let rotation = 0;
  let isAbsoluteAngle = false;
  let isUprightAngle = false;
  let xOffset = 0;
  let yOffset = 0;

  if (parts.length > 0) {
    const first = parts[0];
    if (first.startsWith('"') && first.endsWith('"')) {
      type = 'text';
      text = first.slice(1, -1);
      if (parts.length > 1 && !parts[1].includes('=')) style = parts[1];
    } else {
      type = 'shape';
      shapeName = first;
      if (parts.length > 1 && !parts[1].includes('=')) shxFile = parts[1];
    }

    for (let i = 1; i < parts.length; i++) {
      const part = parts[i];
      const eqIdx = part.indexOf('=');
      if (eqIdx > 0) {
        const key = part.substring(0, eqIdx).trim().toLowerCase();
        const valStr = part.substring(eqIdx + 1).trim();
        const val = parseFloat(valStr);
        if (!isNaN(val)) {
          if (key === 's') scale = val;
          else if (key === 'r') { rotation = val; }
          else if (key === 'a') { rotation = val; isAbsoluteAngle = true; }
          else if (key === 'u') { rotation = val; isUprightAngle = true; }
          else if (key === 'x') xOffset = val;
          else if (key === 'y') yOffset = val;
        }
      }
    }
  }

  return {
    type,
    shapeName,
    shxFile,
    text,
    style,
    scale,
    rotation,
    isAbsoluteAngle,
    isUprightAngle,
    xOffset,
    yOffset,
  };
}

function tokenizeLinPattern(patternStr: string): string[] {
  const s = patternStr.replace(/^A\s*,\s*/i, '').trim();
  const tokens: string[] = [];
  let current = '';
  let inBracket = false;
  let inQuote = false;

  for (let i = 0; i < s.length; i++) {
    const ch = s[i];
    if (ch === '"') inQuote = !inQuote;
    if (ch === '[' && !inQuote) inBracket = true;
    if (ch === ']' && !inQuote) inBracket = false;

    if (ch === ',' && !inBracket && !inQuote) {
      if (current.trim()) tokens.push(current.trim());
      current = '';
    } else {
      current += ch;
    }
  }
  if (current.trim()) tokens.push(current.trim());
  return tokens;
}

export function parseLinContent(content: string): Map<string, LinPattern> {
  const result = new Map<string, LinPattern>();
  const lines = content.split(/\r?\n/);
  let currentName: string | null = null;
  let currentDesc = '';

  for (let line of lines) {
    line = line.trim();
    if (!line || line.startsWith(';')) continue;
    if (line.startsWith('*')) {
      const commaIdx = line.indexOf(',');
      if (commaIdx > 0) {
        currentName = line.substring(1, commaIdx).trim();
        currentDesc = line.substring(commaIdx + 1).trim();
      } else {
        currentName = line.substring(1).trim();
        currentDesc = '';
      }
    } else if (currentName && line.toUpperCase().startsWith('A,')) {
      const tokens = tokenizeLinPattern(line);
      const elements: LinElement[] = [];
      let totalLength = 0;

      for (const tok of tokens) {
        if (tok.startsWith('[') && tok.endsWith(']')) {
          const elem = parseBracketElement(tok.slice(1, -1));
          elements.push(elem);
        } else {
          const val = parseFloat(tok);
          if (!isNaN(val)) {
            if (val > 1e-6) {
              elements.push({
                type: 'dash',
                val,
                scale: 1,
                rotation: 0,
                isAbsoluteAngle: false,
                isUprightAngle: false,
                xOffset: 0,
                yOffset: 0,
              });
              totalLength += val;
            } else if (val < -1e-6) {
              const absVal = Math.abs(val);
              elements.push({
                type: 'gap',
                val: -absVal,
                scale: 1,
                rotation: 0,
                isAbsoluteAngle: false,
                isUprightAngle: false,
                xOffset: 0,
                yOffset: 0,
              });
              totalLength += absVal;
            } else {
              // Dot (val === 0)
              elements.push({
                type: 'dash',
                val: 0.05,
                scale: 1,
                rotation: 0,
                isAbsoluteAngle: false,
                isUprightAngle: false,
                xOffset: 0,
                yOffset: 0,
              });
              totalLength += 0.05;
            }
          }
        }
      }

      if (elements.length > 0) {
        result.set(currentName.toUpperCase(), {
          name: currentName,
          description: currentDesc,
          elements,
          patternLength: Math.max(totalLength, 0.1),
        });
      }
      currentName = null;
    }
  }

  return result;
}

// Global Linetype Registry initialized with Icad.lin and KOSDIC.lin
const globalRegistry = new Map<string, LinPattern>();

export function registerLinContent(content: string) {
  const parsed = parseLinContent(content);
  for (const [key, pattern] of parsed.entries()) {
    globalRegistry.set(key, pattern);
  }
}

// Warm up registry with bundled LIN files
registerLinContent(ICAD_LIN);
registerLinContent(KOSDIC_LIN);

const CIVIL_LT_TEXT_MAP: Record<string, string> = {
  'H-DICHL02': 'L2-2/0',
  'H-DICHL02R': 'L2-2',
  'H-DICHL01': 'L2-3/0',
  'H-DICHL01R': 'L2-3',
  'H-DICHL06': 'L5-1',
  'H-DICHL06R': 'L5-1',
  'H-DICHL05': 'L5',
  'H-DICHL05R': 'L5',
  'H-DICHU05': 'U5',
  'H-DICHU05R': 'U5',
  'H-DICHB01': 'B1',
  'H-DICHB01R': 'B1',
  'H-DICHM03': 'M3',
  'H-DICHM03R': 'M3',
  'H-DICHV01': 'V1',
  'H-DICHV01R': 'V1',
  'H-DICHO01': 'O1',
  'H-DICHO01R': 'O1',
  'H-DICHL03': 'L3',
  'H-DICHL03R': 'L3',
  'H-DICHL04': 'L4',
  'H-DICHL04R': 'L4',
  'H-DICHL08': 'L8',
  'H-DICHL08R': 'L8',
};

const NO_SHAPE_LINETYPES = new Set([
  'H-DICHL05',
  'H-DICHL05R',
  'H-DICHU05',
  'H-DICHU05R',
  'H-DICHB01',
  'H-DICHB01R',
  'H-DICHM03',
  'H-DICHM03R',
  'H-DICHV01',
  'H-DICHV01R',
  'H-DICHO01',
  'H-DICHO01R',
]);

export function getLinPattern(name: string): LinPattern | undefined {
  if (!name) return undefined;
  const key = name.toUpperCase().trim();
  const pattern = globalRegistry.get(key);
  if (!pattern) return undefined;

  let elements = pattern.elements;

  // Filter out shape/arrowhead elements for L5, U5, B1, etc. as specified in official CAD spec
  if (NO_SHAPE_LINETYPES.has(key)) {
    elements = elements.filter((elem) => elem.type !== 'shape');
  }

  const overrideText = CIVIL_LT_TEXT_MAP[key];
  if (overrideText) {
    elements = elements.map((elem) =>
      elem.type === 'text' ? { ...elem, text: overrideText } : elem
    );
  }

  return {
    ...pattern,
    elements,
  };
}

export function getAllRegisteredPatterns(): Map<string, LinPattern> {
  return globalRegistry;
}

export function mergeDwgLinetypePattern(
  ltName: string,
  dwgPattern: number[],
  description?: string
): LinPattern | undefined {
  if (!ltName) return undefined;
  const base = getLinPattern(ltName);
  if (!dwgPattern || !dwgPattern.length) return base;

  if (!base) {
    const elements: LinElement[] = [];
    let totalLength = 0;
    for (const val of dwgPattern) {
      if (val > 1e-6) {
        elements.push({
          type: 'dash',
          val,
          scale: 1,
          rotation: 0,
          isAbsoluteAngle: false,
          isUprightAngle: false,
          xOffset: 0,
          yOffset: 0,
        });
        totalLength += val;
      } else if (val < -1e-6) {
        const absVal = Math.abs(val);
        elements.push({
          type: 'gap',
          val: -absVal,
          scale: 1,
          rotation: 0,
          isAbsoluteAngle: false,
          isUprightAngle: false,
          xOffset: 0,
          yOffset: 0,
        });
        totalLength += absVal;
      }
    }
    return {
      name: ltName,
      description: description || '',
      elements,
      patternLength: Math.max(totalLength, 0.1),
    };
  }

  // Merge DWG numeric dash/gap values into base pattern elements
  const newElements: LinElement[] = [];
  let dwgValIdx = 0;
  let totalLength = 0;

  for (const elem of base.elements) {
    if (elem.type === 'dash') {
      const dwgVal =
        dwgValIdx < dwgPattern.length && dwgPattern[dwgValIdx] > 0
          ? dwgPattern[dwgValIdx]
          : elem.val || 0.05;
      if (dwgValIdx < dwgPattern.length && dwgPattern[dwgValIdx] > 0) {
        dwgValIdx++;
      }
      newElements.push({ ...elem, val: dwgVal });
      totalLength += dwgVal;
    } else if (elem.type === 'gap') {
      const dwgVal =
        dwgValIdx < dwgPattern.length && dwgPattern[dwgValIdx] < 0
          ? dwgPattern[dwgValIdx]
          : elem.val;
      if (dwgValIdx < dwgPattern.length && dwgPattern[dwgValIdx] < 0) {
        dwgValIdx++;
      }
      const absVal = Math.abs(dwgVal || 0.1);
      newElements.push({ ...elem, val: -absVal });
      totalLength += absVal;
    } else {
      newElements.push({ ...elem });
    }
  }

  return {
    ...base,
    description: description || base.description,
    elements: newElements,
    patternLength: Math.max(totalLength, 0.1),
  };
}

