#!/usr/bin/env node
import { readFileSync } from 'node:fs';

const HELP = `Usage: tally [options] <pattern> <file>

Count literal-substring occurrences of <pattern> in <file>.

Options:
  -i, --ignore-case  Case-insensitive matching
      --lines        Count lines containing pattern (not total occurrences)
  -r, --regex        Interpret <pattern> as a JavaScript regular expression
      --json         Emit a JSON object on stdout
  -h, --help         Show this help and exit

Exit codes: 0=success  1=file error  2=invalid argv
`;

function fail(msg, code = 2) {
  process.stderr.write(`tally: ${msg}\n`);
  process.exit(code);
}

function parseArgs(argv) {
  const positional = [];
  const flags = { json: false, ignoreCase: false, lines: false, regex: false };
  for (const a of argv) {
    if (a === '--json') flags.json = true;
    else if (a === '-i' || a === '--ignore-case') flags.ignoreCase = true;
    else if (a === '--lines') flags.lines = true;
    else if (a === '--regex' || a === '-r') flags.regex = true;
    else if (a === '-h' || a === '--help') { process.stdout.write(HELP); process.exit(0); }
    else if (a.startsWith('-')) fail(`unknown flag: ${a}`);
    else positional.push(a);
  }
  if (positional.length !== 2) fail('expected <pattern> <file>');
  return { pattern: positional[0], file: positional[1], flags };
}

function compileRegex(pattern) {
  try { return new RegExp(pattern, 'g'); }
  catch (e) { fail(`invalid regex: ${e.message}`); }
}

function countOccurrences(text, pattern, ignoreCase) {
  if (pattern.length === 0) return 0;
  const haystack = ignoreCase ? text.toLowerCase() : text;
  const needle = ignoreCase ? pattern.toLowerCase() : pattern;
  let count = 0, idx = 0;
  while ((idx = haystack.indexOf(needle, idx)) !== -1) { count++; idx += needle.length; }
  return count;
}

function countLines(text, pattern, ignoreCase) {
  if (pattern.length === 0) return 0;
  const needle = ignoreCase ? pattern.toLowerCase() : pattern;
  let count = 0;
  for (const line of text.split('\n')) {
    const haystack = ignoreCase ? line.toLowerCase() : line;
    if (haystack.includes(needle)) count++;
  }
  return count;
}

function main() {
  const { pattern, file, flags } = parseArgs(process.argv.slice(2));
  let text;
  try {
    text = readFileSync(file, 'utf8');
  } catch (err) {
    const what = err.code === 'ENOENT' ? 'file not found' : 'read error';
    process.stderr.write(`tally: ${what}: ${file}\n`);
    process.exit(1);
  }
  let count;
  if (flags.regex) {
    const re = compileRegex(pattern);
    count = (text.match(re) || []).length;
  } else if (flags.lines) {
    count = countLines(text, pattern, flags.ignoreCase);
  } else {
    count = countOccurrences(text, pattern, flags.ignoreCase);
  }
  if (flags.json) {
    process.stdout.write(JSON.stringify({
      pattern, file, count,
      flags: { json: flags.json, ignoreCase: flags.ignoreCase, lines: flags.lines, regex: flags.regex },
    }) + '\n');
  } else {
    process.stdout.write(count + '\n');
  }
}

main();
