/**
 * Repair Korean text that acadrust emits as Latin-1 mojibake of CP949/EUC-KR.
 *
 * Older Korean DWGs store strings in code page 949. When those bytes are
 * decoded as ISO-8859-1/Windows-1252, Hangul becomes garbled Latin
 * (e.g. "½Ã°ø½Ã" instead of "시공시"). Unicode-correct drawings
 * (already containing Hangul syllables) are left unchanged.
 *
 * Apply after parse_dwg, before Viewer2D.load().
 */

const HANGUL_SYLLABLE = /[\uAC00-\uD7A3]/;
const HANGUL_JAMO = /[\u1100-\u11FF\u3130-\u318F]/;

/** Labels tried in order; first supported decoder wins. */
const KOREAN_LABELS = ['cp949', 'windows-949', 'euc-kr'] as const;

let koreanDecoder: TextDecoder | null | undefined;

function getKoreanDecoder(): TextDecoder | null {
  if (koreanDecoder !== undefined) return koreanDecoder;
  for (const label of KOREAN_LABELS) {
    try {
      koreanDecoder = new TextDecoder(label);
      return koreanDecoder;
    } catch {
      /* label not available in this runtime */
    }
  }
  koreanDecoder = null;
  return null;
}

/**
 * If `s` looks like CP949 bytes misread as Latin-1, return the Hangul
 * form; otherwise return `s` unchanged.
 */
export function fixCp949Mojibake(s: string): string {
  if (!s) return s;
  // Already real Hangul → leave alone (e.g. BasicSample.dwg).
  if (HANGUL_SYLLABLE.test(s) || HANGUL_JAMO.test(s)) return s;

  let hasHigh = false;
  const bytes = new Uint8Array(s.length);
  for (let i = 0; i < s.length; i++) {
    const code = s.charCodeAt(i);
    if (code > 255) return s; // true Unicode beyond Latin-1
    if (code > 127) hasHigh = true;
    bytes[i] = code;
  }
  if (!hasHigh) return s; // pure ASCII

  const decoder = getKoreanDecoder();
  if (!decoder) return s;

  let fixed: string;
  try {
    fixed = decoder.decode(bytes);
  } catch {
    return s;
  }

  // Accept only when we gain Hangul and the decode is clean.
  if (
    (HANGUL_SYLLABLE.test(fixed) || HANGUL_JAMO.test(fixed)) &&
    !fixed.includes('\uFFFD')
  ) {
    return fixed;
  }
  return s;
}

/**
 * Deep-walk a CadParseResult (or any JSON-like tree) and fix string fields
 * in place. Returns the same object for chaining.
 */
export function fixDwgKoreanText<T>(root: T): T {
  if (root == null || typeof root !== 'object') return root;
  const seen = new WeakSet<object>();

  const walk = (value: unknown): void => {
    if (value == null || typeof value !== 'object') return;
    if (seen.has(value as object)) return;
    seen.add(value as object);

    if (Array.isArray(value)) {
      for (let i = 0; i < value.length; i++) {
        const item = value[i];
        if (typeof item === 'string') value[i] = fixCp949Mojibake(item);
        else walk(item);
      }
      return;
    }

    const obj = value as Record<string, unknown>;
    for (const key of Object.keys(obj)) {
      if (key === 'imageDataUrl') continue;
      const item = obj[key];
      if (typeof item === 'string') obj[key] = fixCp949Mojibake(item);
      else walk(item);
    }
  };

  walk(root);
  return root;
}
