import type { APIRoute } from "astro";
import { readFile } from "node:fs/promises";
import { parse } from "toml";

/**
 * Makes sure the copyright follows the TASL format. T = title (or description), A = author, S = source, L = license.
 * @param copyright
 */
function validateTASL(copyright: any) {
  const title = copyright.title ?? copyright.description;
  if (typeof title !== "string" || !title) {
    throw new Error(`Missing "title" and/or "description" for attribution (${JSON.stringify(copyright)})`);
  }
  // Author must be a valid string or object or list
  const authors = [copyright.author].flat();
  if (authors.length === 0) {
    throw new Error(`Missing "author" for attribution "${title}" (${JSON.stringify(copyright)})`);
  }
  authors.forEach((author) => {
    if (!author) {
      throw new Error(`Missing "author" for attribution "${title}" (${JSON.stringify(copyright)})`);
    }
    if (typeof author !== "object" && typeof author !== "string") {
      throw new Error(
        `Invalid "${typeof author}" type for "author"${JSON.stringify(author)} for attribution "${title}" (${JSON.stringify(copyright)})`,
      );
    }
    if (typeof author === "object" && !(author.name || author.url)) {
      throw new Error(
        `Missing both name and URL for "author" ${JSON.stringify(author)} for attribution "${title}" (${JSON.stringify(copyright)})`,
      );
    }
  });
  // Source must be a valid string or list of strings
  const sources = [copyright.source].flat();
  if (sources.length === 0) {
    throw new Error(`Missing "source" for attribution "${title}" (${JSON.stringify(copyright)})`);
  }
  sources.forEach((source) => {
    if (!source) {
      throw new Error(`Missing "source" for attribution "${title}" (${JSON.stringify(copyright)})`);
    }
    if (typeof source !== "object" && typeof source !== "string") {
      throw new Error(
        `Invalid "${typeof source}" type for "source"${JSON.stringify(source)} for attribution "${title}" (${JSON.stringify(copyright)})`,
      );
    }
    if (typeof source === "object" && !source.url) {
      throw new Error(
        `Missing URL for "source" ${JSON.stringify(source)} for attribution "${title}" (${JSON.stringify(copyright)})`,
      );
    }
  });
  // License must be a valid string or object or list
  const licenses = [copyright.license].flat();
  if (licenses.length === 0) {
    throw new Error(`Missing "license" for attribution "${title}" (${JSON.stringify(copyright)})`);
  }
  licenses.forEach((license) => {
    if (!license) {
      throw new Error(`Missing "license" for attribution "${title}" (${JSON.stringify(copyright)})`);
    }
    if (typeof license !== "object" && typeof license !== "string") {
      throw new Error(
        `Invalid "${typeof license}" type for "license"${JSON.stringify(license)} for attribution "${title}" (${JSON.stringify(copyright)})`,
      );
    }
    if (typeof license === "object" && !(license.name || license.url)) {
      throw new Error(
        `Missing both name and URL for "license" ${JSON.stringify(license)} for attribution "${title}" (${JSON.stringify(copyright)})`,
      );
    }
  });
}

/**
 * Verifies attributions and copyright according to the [Creative Commons recommended practices](https://wiki.creativecommons.org/wiki/Recommended_practices_for_attribution)
 * @param copyright Unparsed TOML copyright information.
 */
function verifyAttributions(licenses: string) {
  const { copyright, attributions } = parse(licenses);
  // Make sure each copyright and attribution follows the TASL format,
  // and that other fields (type, notes, items) pass their custom validation.
  [copyright, attributions].flat().forEach((value) => {
    // Validate TASL
    validateTASL(value);
    const title = copyright.title ?? copyright.description;
    // Validate extra optional fields
    // 1. Type must be a valid string
    if (typeof value.type !== "string") {
      throw new Error(`Invalid "type" for attribution "${title}" (${JSON.stringify(value)})`);
    }
    if (!value.type) {
      throw new Error(`Missing "type" for attribution "${title}" (${JSON.stringify(value)})`);
    }
    // 2. Items must be a valid list of strings/objects
    if ("items" in value) {
      const items = value.items;
      if (!Array.isArray(items)) {
        throw new Error(
          `Invalid non-array "items" ${JSON.stringify(items)} for attribution "${title}" (${JSON.stringify(value)})`,
        );
      }
      items.forEach((item) => {
        if (!item) {
          throw new Error(
            `Invalid item ${JSON.stringify} in "items" for attribution "${title}" (${JSON.stringify(value)})`,
          );
        }
      });
    }
    // 3. Type must be a valid string
    if ("notes" in value) {
      if (typeof value.notes !== "string" || !value.notes) {
        throw new Error(`Invalid "notes" for attribution "${title}" (${JSON.stringify(value)})`);
      }
    }
  });
  // Check date for copyrights
  if (typeof copyright.date !== "string") {
    throw new Error(`Invalid "date" for copyright (${JSON.stringify(copyright)})`);
  }
  if (!copyright.date) {
    throw new Error(`Missing "date" for copyright (${JSON.stringify(copyright)})`);
  }
  // Check notes for additional copyrights
  if ("additional" in copyright) {
    const additionals = copyright.additional;
    if (!Array.isArray(additionals)) {
      throw new Error(
        `Invalid non-array "additional" ${JSON.stringify(additionals)} for copyright (${JSON.stringify(copyright)})`,
      );
    }
    additionals.forEach((additional) => {
      if (typeof additional.notes !== "string" || !additional.notes) {
        throw new Error(`Invalid "notes" for additional copyright (${JSON.stringify(additional)})`);
      }
      if (typeof additional.type !== "string" || !additional.type) {
        throw new Error(`Invalid "type" for additional copyright (${JSON.stringify(additional)})`);
      }
      // Check TASL + date if title or description is present
      if (additional.title || additional.description) {
        validateTASL(additional);
        if (typeof additional.date !== "string") {
          throw new Error(`Invalid "date" for additional (${JSON.stringify(additional)})`);
        }
        if (!additional.date) {
          throw new Error(`Missing "date" for additional (${JSON.stringify(additional)})`);
        }
      }
    });
  }
}

export const GET: APIRoute = async () => {
  const licenses = await readFile("./src/data/licenses.toml", { encoding: "utf-8" });
  verifyAttributions(licenses);
  return new Response(licenses, { headers: { "Content-Type": "application/toml; charset=utf-8" } });
};
