import { extractFilePath } from '../hooks/extract-file-path.js';
import { detectMarker } from '../hooks/detect-marker.js';
import { acquireLock, releaseLock } from '../hooks/debounce-lock.js';
import { compileCommand } from './compile.js';

/**
 * Internal options for testing — allows injecting stdin content
 * instead of reading from process.stdin.
 */
export interface HookInternalOptions {
  stdin?: string;
}

/**
 * Read all data from process.stdin as a string.
 */
async function readStdin(): Promise<string> {
  const chunks: Buffer[] = [];
  for await (const chunk of process.stdin) {
    chunks.push(chunk as Buffer);
  }
  return Buffer.concat(chunks).toString('utf-8');
}

/**
 * Parse JSON input, returning the parsed object or null on failure.
 * On parse failure, sets exitCode = 1 and writes a warning to stderr.
 */
function parseInput(raw: string): Record<string, unknown> | null {
  if (raw.trim() === '') return null;
  try {
    const parsed: unknown = JSON.parse(raw);
    if (typeof parsed !== 'object' || parsed === null) return null;
    return parsed as Record<string, unknown>;
  } catch {
    process.exitCode = 1;
    process.stderr.write('prs hook: malformed JSON on stdin\n');
    return null;
  }
}

/**
 * Handle pre-edit action.
 * Checks if the target file was generated by PromptScript and blocks the edit if so.
 */
async function handlePreEdit(payload: Record<string, unknown>): Promise<void> {
  const filePath = extractFilePath(payload);
  if (!filePath) return;

  const marker = await detectMarker(filePath);
  if (!marker.isGenerated) return;

  process.exitCode = 2;
  if (marker.source) {
    process.stderr.write(
      `prs hook: ${filePath} is generated by PromptScript from ${marker.source} — edit the source instead\n`
    );
  } else {
    process.stderr.write(
      `prs hook: ${filePath} is generated by PromptScript — run prs compile after editing the source\n`
    );
  }
}

/**
 * Handle post-edit action.
 * If the edited file is a .prs file, recompile the project.
 */
async function handlePostEdit(payload: Record<string, unknown>): Promise<void> {
  const filePath = extractFilePath(payload);
  if (!filePath) return;
  if (!filePath.endsWith('.prs')) return;

  const cwd = typeof payload['cwd'] === 'string' ? payload['cwd'] : process.cwd();

  if (!acquireLock(cwd)) return;

  try {
    await compileCommand({ cwd });
  } catch (error: unknown) {
    const message = error instanceof Error ? error.message : String(error);
    process.stderr.write(`prs hook: compile error: ${message}\n`);
  } finally {
    releaseLock(cwd);
  }
}

/**
 * Hook command handler invoked by AI tools.
 *
 * @param action - The hook action: 'pre-edit' or 'post-edit'
 * @param internalOpts - Internal options (stdin override for testing)
 */
export async function hookCommand(
  action: string,
  internalOpts?: HookInternalOptions
): Promise<void> {
  if (internalOpts?.stdin === undefined && process.stdin.isTTY) {
    process.exitCode = 1;
    process.stderr.write(
      `prs hook: no input received on stdin. This command is intended to be invoked by AI tools, not directly.\n` +
        `Usage: echo '{"file_path":"..."}' | prs hook ${action}\n`
    );
    return;
  }

  const raw = internalOpts?.stdin !== undefined ? internalOpts.stdin : await readStdin();
  const payload = parseInput(raw);
  if (!payload) return;

  switch (action) {
    case 'pre-edit':
      await handlePreEdit(payload);
      break;
    case 'post-edit':
      await handlePostEdit(payload);
      break;
    default:
      process.exitCode = 1;
      process.stderr.write(`prs hook: unknown action "${action}"\n`);
  }
}
