import * as fs from 'node:fs';
import * as path from 'node:path';
import {
  listInstalledPackageNames,
  packageNameToPath,
  resolvePackagesNodeModulesRoot,
  resolveRegistryRoot,
} from '../utils/package-store.js';
import { getLatestRegistryVersion } from '../utils/registry-store.js';
import { compareSemver } from '../utils/versioning.js';

type JsonRecord = Record<string, unknown>;

function asRecord(value: unknown): JsonRecord | null {
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
    return null;
  }
  return value as JsonRecord;
}

function readForkRecord(forkRecordPath: string): {
  readonly origin: string;
  readonly originVersion: string;
} | null {
  if (!fs.existsSync(forkRecordPath)) {
    return null;
  }
  const parsed = JSON.parse(fs.readFileSync(forkRecordPath, 'utf8')) as unknown;
  const record = asRecord(parsed);
  const origin = typeof record?.origin === 'string' ? record.origin.trim() : '';
  const originVersion = typeof record?.originVersion === 'string' ? record.originVersion.trim() : '';
  if (!origin || !originVersion) {
    return null;
  }
  return { origin, originVersion };
}

export interface IOutdatedCommandOptions {
  readonly cwd?: string;
  readonly packagesDir?: string;
  readonly registryDir?: string;
}

export interface IOutdatedEntry {
  readonly packageName: string;
  readonly origin: string;
  readonly localOriginVersion: string;
  readonly latestOriginVersion: string | null;
  readonly status: 'up-to-date' | 'outdated' | 'missing-upstream';
}

export interface IOutdatedCommandResult {
  readonly entries: ReadonlyArray<IOutdatedEntry>;
}

export async function outdatedCommand(options: IOutdatedCommandOptions = {}): Promise<IOutdatedCommandResult> {
  const cwd = path.resolve(options.cwd ?? process.cwd());
  const nodeModulesRoot = resolvePackagesNodeModulesRoot(cwd, options.packagesDir);
  const registryRoot = resolveRegistryRoot(cwd, options.registryDir);
  const entries: IOutdatedEntry[] = [];

  for (const packageName of listInstalledPackageNames(nodeModulesRoot)) {
    const packageDir = path.join(nodeModulesRoot, packageNameToPath(packageName));
    const forkRecord = readForkRecord(path.join(packageDir, '.forked-from'));
    if (!forkRecord) {
      continue;
    }

    const latestOriginVersion = getLatestRegistryVersion(registryRoot, forkRecord.origin);
    if (!latestOriginVersion) {
      entries.push({
        packageName,
        origin: forkRecord.origin,
        localOriginVersion: forkRecord.originVersion,
        latestOriginVersion: null,
        status: 'missing-upstream',
      });
      continue;
    }

    entries.push({
      packageName,
      origin: forkRecord.origin,
      localOriginVersion: forkRecord.originVersion,
      latestOriginVersion,
      status: compareSemver(latestOriginVersion, forkRecord.originVersion) > 0 ? 'outdated' : 'up-to-date',
    });
  }

  return { entries };
}
