import ts from "typescript";
import { IJsDocTagInfo } from "typia";

import { INestiaProject } from "../../structures/INestiaProject";
import { ITypedHttpRoute } from "../../structures/ITypedHttpRoute";
import { FilePrinter } from "./FilePrinter";
import { ImportDictionary } from "./ImportDictionary";
import { SdkHttpFunctionProgrammer } from "./SdkHttpFunctionProgrammer";
import { SdkHttpNamespaceProgrammer } from "./SdkHttpNamespaceProgrammer";

export namespace SdkHttpRouteProgrammer {
  export const write =
    (project: INestiaProject) =>
    (importer: ImportDictionary) =>
    (route: ITypedHttpRoute): ts.Statement[] => {
      const props = {
        headers: route.parameters
          .filter((p) => p.category === "headers")
          .find((p) => p.field === null),
        query: route.parameters
          .filter((p) => p.category === "query")
          .find((p) => p.field === null),
        input: route.parameters.find((p) => p.category === "body"),
      };
      return [
        FilePrinter.description(
          SdkHttpFunctionProgrammer.write(project)(importer)(route, props),
          describe(route),
        ),
        SdkHttpNamespaceProgrammer.write(project)(importer)(route, props),
      ];
    };

  const describe = (route: ITypedHttpRoute): string => {
    // MAIN DESCRIPTION
    const descriptionComments: string[] = route.description
      ? route.description.split("\n")
      : [];
    const tagComments: string[] = [];

    // PARAMETERS
    for (const p of route.parameters) {
      if (p.category === "headers") continue;

      const description: string | undefined =
        p.description ??
        p.jsDocTags.find((tag) => tag.name === "description")?.text?.[0].text ??
        route.jsDocTags
          .find((tag) => tag.name === "param" && tag.text?.[0].text === p.name)
          ?.text?.map((e) => e.text)
          .join("")
          .substring(p.name.length);
      if (!description?.length) continue;

      tagComments.push(
        `@param ${p.name} ${description
          .split("\n")
          .map((str) => str.trim())
          .map((str, i) => {
            if (i === 0) return str;
            const rpad: number = p.name.length + 8;
            return `${" ".repeat(rpad)}${str}`;
          })
          .join("\n")}`,
      );
    }

    // COMMENT TAGS
    const tags: IJsDocTagInfo[] = route.jsDocTags.filter(
      (tag) => tag.name !== "param",
    );
    if (tags.length !== 0) {
      const content: string[] = tags.map((t) =>
        t.text?.length
          ? `@${t.name} ${t.text
              .map((e) => e.text)
              .join("")
              .split("\n")
              .map((str) => str.trim())
              .map((str, i) => {
                if (i === 0) return str;
                return `${" ".repeat(t.name.length + 1)} ${str}`;
              })
              .join("\n")}`
          : `@${t.name}`,
      );
      tagComments.push(...new Set(content));
    }

    // EXCEPTIONS
    for (const [key, value] of Object.entries(route.exceptions)) {
      if (
        tagComments.some(
          (str) =>
            str.startsWith(`@throw ${key}`) || str.startsWith(`@throws ${key}`),
        )
      )
        continue;
      tagComments.push(
        value.description?.length
          ? `@throws ${key} ${value.description.split("\n")[0]}`
          : `@throws ${key}`,
      );
    }

    // POSTFIX
    return [
      ...descriptionComments,
      ...(descriptionComments.length && tagComments.length ? [""] : []),
      ...tagComments,
      ...(descriptionComments.length && tagComments.length ? [""] : []),
      `@controller ${route.controller.class.name}.${route.name}`,
      `@path ${route.method} ${route.path}`,
      `@nestia Generated by Nestia - https://github.com/samchon/nestia`,
    ].join("\n");
  };
}
