/** Local GOA AST mutations (runs in Workbench/Tauri — no remote file access). */

export type LocalAstOperation =
  | 'ADD_IMPORT'
  | 'REPLACE_BODY'
  | 'INJECT_WRAPPER'
  | 'APPEND_PROPERTY'
  | 'INSERT_BEFORE';

export interface LocalAstMutationInput {
  source: string;
  operation: LocalAstOperation;
  targetNode?: string;
  codePayload?: string;
  filePath?: string;
}

export interface LocalAstMutationResult {
  content: string;
  applied: boolean;
}

function detectLang(filePath?: string): 'javascript' | 'typescript' | 'python' | 'json' | 'text' {
  const ext = (filePath || '').split('.').pop()?.toLowerCase();
  if (ext === 'tsx' || ext === 'ts') return 'typescript';
  if (ext === 'jsx' || ext === 'js') return 'javascript';
  if (ext === 'py') return 'python';
  if (ext === 'json') return 'json';
  return 'text';
}

function addImport(source: string, importClause: string, lang: ReturnType<typeof detectLang>): LocalAstMutationResult {
  const clause = importClause.trim();
  if (!clause || source.includes(clause)) return { content: source, applied: false };
  const lines = source.split('\n');
  let insertAt = 0;
  if (lang === 'javascript' || lang === 'typescript') {
    for (let i = 0; i < lines.length; i += 1) {
      if (lines[i].startsWith('import ') || lines[i].startsWith('export import')) insertAt = i + 1;
    }
    lines.splice(insertAt, 0, clause);
    return { content: lines.join('\n'), applied: true };
  }
  if (lang === 'python') {
    for (let i = 0; i < lines.length; i += 1) {
      if (lines[i].startsWith('import ') || lines[i].startsWith('from ')) insertAt = i + 1;
    }
    lines.splice(insertAt, 0, clause);
    return { content: lines.join('\n'), applied: true };
  }
  return { content: `${clause}\n${source}`, applied: true };
}

function replaceFunctionBody(
  source: string,
  targetNode: string,
  newBody: string,
  lang: ReturnType<typeof detectLang>,
): LocalAstMutationResult {
  const name = targetNode.replace(/^function:/, '').trim();
  const body = newBody.trim();
  if (!name || !body) return { content: source, applied: false };

  if (lang === 'javascript' || lang === 'typescript') {
    const patterns = [
      new RegExp(`(export\\s+(?:async\\s+)?function\\s+${name}\\s*\\([^)]*\\)\\s*\\{)([\\s\\S]*?)(\\n\\})`),
      new RegExp(`(function\\s+${name}\\s*\\([^)]*\\)\\s*\\{)([\\s\\S]*?)(\\n\\})`),
      new RegExp(`(${name}\\s*=\\s*(?:async\\s*)?\\([^)]*\\)\\s*=>\\s*\\{)([\\s\\S]*?)(\\n\\})`),
    ];
    for (const pattern of patterns) {
      const match = source.match(pattern);
      if (match && match.index !== undefined) {
        const replaced =
          source.slice(0, match.index + match[1].length) +
          `\n${body}\n` +
          source.slice(match.index + match[1].length + match[2].length);
        return { content: replaced, applied: true };
      }
    }
  }

  if (lang === 'python') {
    const pattern = new RegExp(`(def\\s+${name}\\s*\\([^)]*\\)\\s*:)([\\s\\S]*?)(?=\\ndef |\\nclass |\\n@|$)`);
    const match = source.match(pattern);
    if (match && match.index !== undefined) {
      const formatted = body
        .split('\n')
        .map((line) => (line.trim() ? `    ${line}` : line))
        .join('\n');
      const replaced =
        source.slice(0, match.index + match[1].length) + `\n${formatted}\n` + source.slice(match.index + match[0].length);
      return { content: replaced, applied: true };
    }
  }

  return { content: source, applied: false };
}

function injectWrapper(
  source: string,
  targetNode: string,
  wrapperPayload: string,
): LocalAstMutationResult {
  const wrapper = (wrapperPayload || targetNode).trim();
  if (!wrapper) return { content: source, applied: false };
  if (source.includes('return (')) {
    const idx = source.indexOf('return (');
    const replaced = `${source.slice(0, idx + 'return ('.length)}${wrapper}(\n    ${source.slice(idx + 'return ('.length + 1)}`;
    if (replaced !== source) {
      const closeIdx = replaced.lastIndexOf(');\n}');
      const content =
        closeIdx === -1 ? replaced : `${replaced.slice(0, closeIdx)}\n    ));\n}${replaced.slice(closeIdx + 4)}`;
      return { content, applied: true };
    }
  }
  return { content: source, applied: false };
}

function appendProperty(source: string, key: string, value: string, objectName?: string): LocalAstMutationResult {
  try {
    const data = JSON.parse(source) as Record<string, unknown>;
    const target =
      objectName && data[objectName] && typeof data[objectName] === 'object'
        ? (data[objectName] as Record<string, unknown>)
        : data;
    let parsed: unknown = value;
    try {
      parsed = JSON.parse(value);
    } catch {
      if (value === 'true' || value === 'false') parsed = value === 'true';
      else if (/^\d+$/.test(value)) parsed = Number(value);
      else parsed = value.replace(/^["']|["']$/g, '');
    }
    target[key] = parsed;
    return { content: `${JSON.stringify(data, null, 2)}\n`, applied: true };
  } catch {
    return { content: source, applied: false };
  }
}

function insertBefore(source: string, targetNode: string, codePayload: string): LocalAstMutationResult {
  const idx = source.indexOf(targetNode);
  if (idx === -1 || !codePayload.trim()) return { content: source, applied: false };
  const block = codePayload.endsWith('\n') ? codePayload : `${codePayload}\n`;
  return { content: source.slice(0, idx) + block + source.slice(idx), applied: true };
}

export function applyLocalAstMutation(input: LocalAstMutationInput): LocalAstMutationResult {
  const lang = detectLang(input.filePath);
  const op = input.operation;
  const target = input.targetNode || '';
  const payload = input.codePayload || '';

  switch (op) {
    case 'ADD_IMPORT':
      return addImport(input.source, payload || target, lang);
    case 'REPLACE_BODY':
      return replaceFunctionBody(input.source, target, payload, lang);
    case 'INJECT_WRAPPER':
      return injectWrapper(input.source, target, payload);
    case 'APPEND_PROPERTY': {
      const [key, ...rest] = (payload || target).split('=');
      return appendProperty(input.source, key.trim(), rest.join('=').trim());
    }
    case 'INSERT_BEFORE':
      return insertBefore(input.source, target, payload);
    default:
      return { content: input.source, applied: false };
  }
}
