import fs from "node:fs/promises";
import path from "node:path";
import type { ScannerLearningLoadResult, ScannerLearningRepositoryPort, ScannerLearningRules, ScannerLearningSaveResult } from "./contracts.js";

export class ScannerLearningRepository implements ScannerLearningRepositoryPort {
  private readonly filePath: string;

  constructor(userDataPath: string, fileName = "scanner-learning.json") {
    this.filePath = path.join(userDataPath, fileName);
  }

  async load(): Promise<ScannerLearningLoadResult> {
    try {
      const raw = await fs.readFile(this.filePath, "utf8");
      const parsed = JSON.parse(raw) as ScannerLearningRules;
      return {
        ok: true,
        path: this.filePath,
        rules: parsed && typeof parsed === "object" ? parsed : emptyRules(),
      };
    } catch {
      return { ok: true, path: this.filePath, rules: emptyRules() };
    }
  }

  async save(rules: ScannerLearningRules): Promise<ScannerLearningSaveResult> {
    const current = await this.load();
    const payload: ScannerLearningRules = mergeRules(current.rules, rules);
    await fs.mkdir(path.dirname(this.filePath), { recursive: true });
    await fs.writeFile(this.filePath, JSON.stringify(payload, null, 2), "utf8");
    return { ok: true, path: this.filePath, rules: payload, total: countRules(payload) };
  }
}

function emptyRules(): ScannerLearningRules {
  return {
    textReplacements: {},
    fieldAliases: {},
    constrainedFixes: {},
    cropAdjustments: {},
    uiProfileAdjustments: {},
  };
}

function mergeRules(current: ScannerLearningRules, incoming: ScannerLearningRules): ScannerLearningRules {
  return {
    textReplacements: {
      ...(current.textReplacements ?? {}),
      ...(incoming.textReplacements ?? {}),
    },
    fieldAliases: mergeNested(current.fieldAliases, incoming.fieldAliases),
    constrainedFixes: {
      ...(current.constrainedFixes ?? {}),
      ...(incoming.constrainedFixes ?? {}),
    },
    cropAdjustments: {
      ...(current.cropAdjustments ?? {}),
      ...(incoming.cropAdjustments ?? {}),
    },
    uiProfileAdjustments: {
      ...(current.uiProfileAdjustments ?? {}),
      ...(incoming.uiProfileAdjustments ?? {}),
    },
  };
}

function mergeNested(
  current: Record<string, Record<string, string>> | undefined,
  incoming: Record<string, Record<string, string>> | undefined,
) {
  const merged: Record<string, Record<string, string>> = {};
  for (const [field, values] of Object.entries(current ?? {})) merged[field] = { ...(values ?? {}) };
  for (const [field, values] of Object.entries(incoming ?? {})) merged[field] = { ...(merged[field] ?? {}), ...(values ?? {}) };
  return merged;
}

function countRules(rules: ScannerLearningRules) {
  const fieldAliases = Object.values(rules.fieldAliases ?? {}).reduce((sum, aliases) => sum + Object.keys(aliases ?? {}).length, 0);
  return (
    Object.keys(rules.textReplacements ?? {}).length
    + fieldAliases
    + Object.keys(rules.constrainedFixes ?? {}).length
    + Object.keys(rules.cropAdjustments ?? {}).length
    + Object.keys(rules.uiProfileAdjustments ?? {}).length
  );
}
