import { existsSync, readFileSync, rmSync, statSync } from 'node:fs';
import { join } from 'node:path';
import { spawnSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import { tmpdir } from 'node:os';

const readInput = () => {
  try {
    const rawInput = readFileSync(0, 'utf8').trim();
    return rawInput ? JSON.parse(rawInput) : {};
  } catch {
    return {};
  }
};

const runGit = (args) => {
  const result = spawnSync('git', args, { encoding: 'utf8' });
  return result.status === 0 ? result.stdout.split('\n').filter(Boolean) : [];
};

const runCommand = (command, args) =>
  spawnSync(command, args, {
    encoding: 'utf8',
    shell: false,
  });

const getStatePath = (input) => {
  const turnId = String(input.turn_id ?? 'unknown').replace(/[^a-zA-Z0-9_.-]/g, '_');
  const cwdHash = createHash('sha256').update(process.cwd()).digest('hex').slice(0, 16);
  return join(tmpdir(), 'codex-fastboard-hooks', cwdHash, `${turnId}.json`);
};

const isTypeScriptSource = (file) => /^src\/.+\.tsx?$/.test(file);

const getDirtyTypeScriptFiles = () => {
  const files = [
    ...runGit(['diff', '--name-only', '--diff-filter=ACMR']),
    ...runGit(['diff', '--cached', '--name-only', '--diff-filter=ACMR']),
    ...runGit(['ls-files', '--others', '--exclude-standard']),
  ];

  return [...new Set(files)].filter(isTypeScriptSource).sort();
};

const getFileState = (file) => {
  try {
    const stat = statSync(file);
    return { mtimeMs: stat.mtimeMs, size: stat.size };
  } catch {
    return null;
  }
};

const hasChangedSinceSnapshot = (beforeState, file) => {
  const before = beforeState[file];
  const current = getFileState(file);

  if (!current) {
    return false;
  }

  return !before || before.mtimeMs !== current.mtimeMs || before.size !== current.size;
};

const firstLines = (value, maxLines = 30) =>
  value
    .split('\n')
    .filter(Boolean)
    .slice(0, maxLines)
    .join('\n');

const report = (message) => {
  process.stdout.write(
    JSON.stringify({
      continue: true,
      systemMessage: message,
    }),
  );
};

const input = readInput();
const statePath = getStatePath(input);
const beforeState = existsSync(statePath) ? JSON.parse(readFileSync(statePath, 'utf8')) : {};
const files = getDirtyTypeScriptFiles().filter((file) => hasChangedSinceSnapshot(beforeState, file));

if (existsSync(statePath)) {
  rmSync(statePath, { force: true });
}

if (files.length === 0) {
  process.exit(0);
}

const prettier = runCommand('npx', ['prettier', '--write', ...files]);

if (prettier.status !== 0) {
  report(`Prettier hook failed:\n${firstLines(`${prettier.stdout}\n${prettier.stderr}`)}`);
  process.exit(0);
}

const eslint = runCommand('npx', [
  'eslint',
  '--no-error-on-unmatched-pattern',
  '--max-warnings=0',
  ...files,
]);

if (eslint.status !== 0) {
  report(`ESLint hook found issues:\n${firstLines(`${eslint.stdout}\n${eslint.stderr}`)}`);
}
