// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
//
// SPDX-License-Identifier: GPL-3.0-or-later

import { readFile } from "node:fs/promises";
import { resolve } from "node:path";
import { Value } from "@sinclair/typebox/value";
import { defaultConfig } from "./defaults.js";
import { ConfigSchema, PartialConfigSchema } from "./schema.js";
import type { RumiloConfig } from "./schema.js";
import { ConfigError } from "../util/errors.js";
import toml from "toml";

export interface LoadedConfig {
  config: RumiloConfig;
  path?: string;
}

function mergeConfig(base: RumiloConfig, override: Partial<RumiloConfig>): RumiloConfig {
  return {
    defaults: {
      ...base.defaults,
      ...override.defaults,
    },
    web: {
      ...base.web,
      ...override.web,
    },
    repo: {
      ...base.repo,
      ...override.repo,
    },
    custom_models: {
      ...base.custom_models,
      ...override.custom_models,
    },
  };
}

function validatePartialConfig(parsed: unknown): asserts parsed is Partial<RumiloConfig> {
  if (!Value.Check(PartialConfigSchema, parsed)) {
    const errors = [...Value.Errors(PartialConfigSchema, parsed)];
    const details = errors
      .map((e) => `  ${e.path}: ${e.message} (got ${JSON.stringify(e.value)})`)
      .join("\n");
    throw new ConfigError(
      `Invalid config:\n${details}`,
    );
  }
}

function validateFullConfig(config: unknown): asserts config is RumiloConfig {
  if (!Value.Check(ConfigSchema, config)) {
    const errors = [...Value.Errors(ConfigSchema, config)];
    const details = errors
      .map((e) => `  ${e.path}: ${e.message} (got ${JSON.stringify(e.value)})`)
      .join("\n");
    throw new ConfigError(
      `Invalid merged config:\n${details}`,
    );
  }
}

export async function loadConfig(): Promise<LoadedConfig> {
  const base = structuredClone(defaultConfig);
  const configHome = process.env["XDG_CONFIG_HOME"] || `${process.env["HOME"] ?? ""}/.config`;
  const configPath = resolve(configHome, "rumilo", "config.toml");

  try {
    const raw = await readFile(configPath, "utf8");
    const parsed: unknown = toml.parse(raw);
    validatePartialConfig(parsed);
    const merged = mergeConfig(base, parsed);
    validateFullConfig(merged);
    return { config: merged, path: configPath };
  } catch (error: any) {
    if (error?.code === "ENOENT") {
      validateFullConfig(base);
      return { config: base };
    }

    if (error instanceof ConfigError) {
      throw error;
    }

    if (error instanceof Error) {
      throw new ConfigError(error.message);
    }

    throw new ConfigError(String(error));
  }
}

export function applyConfigOverrides(
  config: RumiloConfig,
  overrides: Partial<RumiloConfig>,
): RumiloConfig {
  const merged = mergeConfig(config, overrides);
  validateFullConfig(merged);
  return merged;
}
