import {
  hyperloopProcess,
  templateMatch,
  templateScaffold,
} from '@/lib/vigthoria-services';
import { applyLocalAstMutation } from '@/lib/goa-ast-local';
import { scanLocalWorkspaceMetadata } from '@/lib/goa-metadata-local';
import {
  deleteWorkspaceFile,
  pathExists,
  readWorkspaceFile,
  runWorkspaceStabilizeSandbox,
  writeWorkspaceFile,
} from '@/lib/tauri-bridge';
import type { GoaExecutionPayload, GoaExecutionResult } from '@/types/goa-orchestrator';

export interface GoaExecutorContext {
  hyperloopUrl: string;
  templateUrl: string;
  token: string | null;
  workspaceRoot: string;
  userPrompt: string;
}

function applyTemplateVariables(html: string, vars: Record<string, string>): string {
  return html.replace(/\{\{([A-Z0-9_]+)\}\}/g, (_, key: string) => vars[key] ?? `{{${key}}}`);
}

function resolveTemplateRelativePath(templateId: string, targetDir?: string, fileName?: string): string {
  const dir = (targetDir || './').replace(/\\/g, '/').replace(/\/+$/, '');
  const slug = fileName || `${(templateId || 'index').replace(/[^a-z0-9-]/gi, '')}.html`;
  if (!dir || dir === '.' || dir === './') return slug;
  return `${dir.replace(/^\.\//, '')}/${slug}`;
}

function parseCompilerDiagnostics(output: string): { file?: string; line?: number; message: string }[] {
  const diagnostics: { file?: string; line?: number; message: string }[] = [];
  for (const line of output.split('\n')) {
    const ts = line.match(/^(.+\.(tsx?|jsx?))\((\d+),\d+\):\s+error\s+TS\d+:\s+(.+)$/);
    if (ts) {
      diagnostics.push({ file: ts[1], line: Number(ts[3]), message: ts[4] });
      continue;
    }
    const eslint = line.match(/^(.+\.(tsx?|jsx?|js)):(\d+):\d+:\s+(.+)$/);
    if (eslint) {
      diagnostics.push({ file: eslint[1], line: Number(eslint[3]), message: eslint[4] });
    }
  }
  return diagnostics.slice(0, 10);
}

/** Layer 2 + 3 — execute GOA instructions locally (files) + remote services (templates/planner). */
export async function executeGoaPayload(
  payload: GoaExecutionPayload,
  ctx: GoaExecutorContext,
): Promise<GoaExecutionResult[]> {
  const results: GoaExecutionResult[] = [];
  const journal = new Map<string, string | null>();

  const captureOriginal = async (relativePath: string): Promise<void> => {
    if (journal.has(relativePath)) return;
    const exists = await pathExists(`${ctx.workspaceRoot}/${relativePath}`);
    journal.set(
      relativePath,
      exists ? await readWorkspaceFile(ctx.workspaceRoot, relativePath) : null,
    );
  };

  const writeTracked = async (
    relativePath: string,
    content: string,
    mergeMode: 'overwrite' | 'skip_if_exists' | 'merge_html_body',
  ) => {
    await captureOriginal(relativePath);
    return writeWorkspaceFile(ctx.workspaceRoot, relativePath, content, mergeMode);
  };

  for (const instruction of [...payload.instructions].sort((a, b) => a.sequence - b.sequence)) {
    try {
      if (instruction.engine === 'CLI_TEMPLATE' && instruction.command === 'EXECUTE_TEMPLATE_INJECTION') {
        const templateId = instruction.payload.template_id || 'hello-world-minimal';
        const vision = [
          ctx.userPrompt,
          `template:${templateId}`,
          Object.entries(instruction.payload.variables || {})
            .map(([k, v]) => `${k}=${v}`)
            .join(' '),
        ]
          .filter(Boolean)
          .join('\n');

        const [raw, scaffoldRaw] = await Promise.all([
          templateMatch(ctx.templateUrl, ctx.token, vision),
          templateScaffold(ctx.templateUrl, ctx.token, vision).catch(() => null),
        ]);

        const html = typeof raw.html === 'string' ? raw.html : null;
        const writes: string[] = [];

        if (html) {
          const vars = instruction.payload.variables || {};
          const filled = applyTemplateVariables(html, vars);

          const scaffold = scaffoldRaw?.scaffold as { file_plan?: string[] } | undefined;
          const filePlan = scaffold?.file_plan?.length ? scaffold.file_plan : ['index.html'];
          const primary = filePlan[0] || 'index.html';
          const primaryPath = resolveTemplateRelativePath(templateId, instruction.payload.target_dir, primary);

          const indexExists = primaryPath.endsWith('index.html')
            ? await pathExists(`${ctx.workspaceRoot}/${primaryPath}`)
            : false;
          const mergeMode = indexExists ? 'merge_html_body' : 'overwrite';
          const primaryWrite = await writeTracked(
            primaryPath,
            filled,
            mergeMode,
          );
          writes.push(
            primaryWrite.merged
              ? `Merged ${primaryPath}`
              : primaryWrite.skipped
                ? `Skipped ${primaryPath}`
                : `Wrote ${primaryPath}`,
          );

          for (const extra of filePlan.slice(1, 6)) {
            const stub = `<!DOCTYPE html><html><head><title>{{TITLE}}</title></head><body><!-- GOA scaffold stub: ${extra} --></body></html>`;
            const stubFilled = applyTemplateVariables(stub, vars);
            const extraPath = resolveTemplateRelativePath(templateId, instruction.payload.target_dir, extra);
            const exists = await pathExists(`${ctx.workspaceRoot}/${extraPath}`);
            if (!exists) {
              await writeTracked(extraPath, stubFilled, 'overwrite');
              writes.push(`Scaffold stub ${extraPath}`);
            }
          }
        }

        results.push({
          payload,
          layer: 'execution',
          success: !!raw.success,
          output: writes.length ? writes.join('\n') : typeof raw.message === 'string' ? raw.message : undefined,
          raw: { match: raw, scaffold: scaffoldRaw ?? undefined } as Record<string, unknown>,
        });
        continue;
      }

      if (instruction.engine === 'AST_TRANSFORMER') {
        const relFile = instruction.payload.file_path?.replace(/\\/g, '/').replace(/^\.\//, '');
        const operation = instruction.payload.operation || 'REPLACE_BODY';

        if (!relFile) {
          results.push({
            payload,
            layer: 'execution',
            success: false,
            error: 'AST mutation requires file_path',
          });
          continue;
        }

        try {
          const source = await readWorkspaceFile(ctx.workspaceRoot, relFile);
          const mutation = applyLocalAstMutation({
            source,
            operation,
            targetNode: instruction.payload.target_node,
            codePayload: instruction.payload.code_payload || instruction.payload.query,
            filePath: relFile,
          });

          if (mutation.applied) {
            await writeTracked(relFile, mutation.content, 'overwrite');
            results.push({
              payload,
              layer: 'execution',
              success: true,
              output: `Local AST ${operation} applied to ${relFile}`,
            });
          } else {
            results.push({
              payload,
              layer: 'execution',
              success: false,
              error: `Local AST ${operation} made no changes in ${relFile}`,
            });
          }
        } catch (err: unknown) {
          results.push({
            payload,
            layer: 'execution',
            success: false,
            error: err instanceof Error ? err.message : String(err),
          });
        }
        continue;
      }

      if (instruction.engine === 'STABILIZATION') {
        const report = await runWorkspaceStabilizeSandbox(ctx.workspaceRoot);
        const diagnostics = parseCompilerDiagnostics(report.output);
        results.push({
          payload,
          layer: 'stabilization',
          success: report.success,
          output:
            diagnostics.length > 0
              ? `${report.output}\n\n--- Parsed diagnostics ---\n${diagnostics.map((d) => `${d.file}:${d.line} ${d.message}`).join('\n')}`
              : report.output,
          error: report.success ? undefined : report.output,
          raw: { diagnostics } as Record<string, unknown>,
        });
        continue;
      }

      results.push({
        payload,
        layer: 'execution',
        success: false,
        error: `Unknown instruction: ${instruction.engine}/${instruction.command}`,
      });
    } catch (err: unknown) {
      results.push({
        payload,
        layer: 'execution',
        success: false,
        error: err instanceof Error ? err.message : String(err),
      });
    }
  }

  if (results.some((result) => !result.success) && journal.size > 0) {
    const rollbackErrors: string[] = [];
    for (const [relativePath, original] of [...journal.entries()].reverse()) {
      try {
        if (original === null) {
          await deleteWorkspaceFile(ctx.workspaceRoot, relativePath);
        } else {
          await writeWorkspaceFile(ctx.workspaceRoot, relativePath, original, 'overwrite');
        }
      } catch (error) {
        rollbackErrors.push(
          `${relativePath}: ${error instanceof Error ? error.message : String(error)}`,
        );
      }
    }
    results.push({
      payload,
      layer: 'execution',
      success: rollbackErrors.length === 0,
      output: rollbackErrors.length === 0
        ? `Transaction rolled back ${journal.size} changed file(s).`
        : undefined,
      error: rollbackErrors.length > 0
        ? `Rollback incomplete:\n${rollbackErrors.join('\n')}`
        : undefined,
      raw: { rolledBack: rollbackErrors.length === 0, changedFiles: [...journal.keys()] },
    });
  }

  return results;
}

/** Metadata tree — local scan first (Workbench has filesystem access). */
export async function fetchAstMetadataTree(
  ctx: Pick<GoaExecutorContext, 'hyperloopUrl' | 'token' | 'workspaceRoot' | 'userPrompt'>,
): Promise<Record<string, unknown> | null> {
  try {
    const local = await scanLocalWorkspaceMetadata(ctx.workspaceRoot, ctx.userPrompt);
    return local as unknown as Record<string, unknown>;
  } catch {
    return null;
  }
}

/** Compiler-error reflex — revise failed instruction payload via Hyper-Loop planner. */
export async function revisePayloadOnCompilerError(
  payload: GoaExecutionPayload,
  errorStack: string,
  ctx: GoaExecutorContext,
): Promise<GoaExecutionPayload | null> {
  if (!ctx.token) return null;
  const diagnostics = parseCompilerDiagnostics(errorStack);
  const failed = payload.instructions.find((i) => i.engine === 'AST_TRANSFORMER');
  const prompt = [
    'COMPILER ERROR REFLEX — output ONLY valid JSON GoaExecutionPayload.',
    'Fix the failed AST instruction properties only.',
    `Diagnostics: ${JSON.stringify(diagnostics)}`,
    `Failed instruction: ${JSON.stringify(failed)}`,
    `Error excerpt:\n${errorStack.slice(0, 2000)}`,
    `Current payload:\n${JSON.stringify(payload)}`,
  ].join('\n\n');
  try {
    const raw = await hyperloopProcess(ctx.hyperloopUrl, ctx.token, prompt, ctx.workspaceRoot);
    const text = typeof raw.result === 'string' ? raw.result : JSON.stringify(raw.result ?? raw);
    const match = text.match(/\{[\s\S]*"workspace_type"[\s\S]*\}/);
    if (!match) return null;
    return JSON.parse(match[0]) as GoaExecutionPayload;
  } catch {
    return null;
  }
}

export function formatExecutionResults(results: GoaExecutionResult[]): string {
  return results
    .map((r, i) => {
      const head = `[${i + 1}] ${r.layer.toUpperCase()} — ${r.success ? 'OK' : 'FAIL'}`;
      const body = r.output || r.error || '';
      return `${head}\n${body}`.trim();
    })
    .join('\n\n');
}
