import ts from "typescript";

import { IdentifierFactory } from "../../factories/IdentifierFactory";
import { StatementFactory } from "../../factories/StatementFactory";
import { TypeFactory } from "../../factories/TypeFactory";

import { Escaper } from "../../utils/Escaper";

import { metadata_to_pattern } from "../internal/metadata_to_pattern";
import { IExpressionEntry } from "./IExpressionEntry";

export namespace NotationJoiner {
  export const object =
    (rename: (str: string) => string) =>
    (
      input: ts.Expression,
      entries: IExpressionEntry<ts.Expression>[],
    ): ts.ConciseBody => {
      if (entries.length === 0) return ts.factory.createIdentifier("{}");

      const regular = entries.filter((e) => e.key.isSoleLiteral());
      const dynamic = entries.filter((e) => !e.key.isSoleLiteral());
      const literal = ts.factory.createObjectLiteralExpression(
        regular.map((entry) => {
          const str: string = rename(entry.key.getSoleLiteral()!);
          return ts.factory.createPropertyAssignment(
            Escaper.variable(str) ? str : ts.factory.createStringLiteral(str),
            entry.expression,
          );
        }),
        true,
      );
      if (dynamic.length === 0) return literal;

      const key = ts.factory.createIdentifier("key");
      const output = ts.factory.createIdentifier("output");

      const statements: ts.Statement[] = [];
      if (regular.length !== 0)
        statements.push(
          ts.factory.createIfStatement(
            ts.factory.createCallExpression(
              IdentifierFactory.access(
                ts.factory.createArrayLiteralExpression(
                  regular.map((r) =>
                    ts.factory.createStringLiteral(r.key.getSoleLiteral()!),
                  ),
                ),
              )("some"),
              undefined,
              [
                ts.factory.createArrowFunction(
                  undefined,
                  undefined,
                  [IdentifierFactory.parameter("regular")],
                  undefined,
                  undefined,
                  ts.factory.createStrictEquality(
                    ts.factory.createIdentifier("regular"),
                    ts.factory.createIdentifier("key"),
                  ),
                ),
              ],
            ),
            ts.factory.createContinueStatement(),
          ),
        );
      statements.push(
        ...dynamic.map((entry) =>
          ts.factory.createIfStatement(
            ts.factory.createCallExpression(
              ts.factory.createIdentifier(
                `RegExp(/${metadata_to_pattern(true)(entry.key)}/).test`,
              ),
              undefined,
              [key],
            ),
            ts.factory.createBlock([
              ts.factory.createExpressionStatement(
                ts.factory.createBinaryExpression(
                  ts.factory.createElementAccessExpression(output, key),
                  ts.factory.createToken(ts.SyntaxKind.EqualsToken),
                  entry.expression,
                ),
              ),
              ts.factory.createContinueStatement(),
            ]),
          ),
        ),
      );

      return ts.factory.createBlock([
        StatementFactory.constant(
          "output",
          ts.factory.createAsExpression(literal, TypeFactory.keyword("any")),
        ),
        ts.factory.createForOfStatement(
          undefined,
          StatementFactory.entry("key")("value"),
          ts.factory.createCallExpression(
            ts.factory.createIdentifier("Object.entries"),
            undefined,
            [input],
          ),
          ts.factory.createBlock(statements),
        ),
        ts.factory.createReturnStatement(output),
      ]);
    };

  export const tuple = (
    children: ts.Expression[],
    rest: ts.Expression | null,
  ): ts.Expression => {
    return ts.factory.createAsExpression(
      ts.factory.createArrayLiteralExpression(
        rest === null
          ? children
          : [...children, ts.factory.createSpreadElement(rest)],
        true,
      ),
      TypeFactory.keyword("any"),
    );
  };

  export const array = (input: ts.Expression, arrow: ts.Expression) =>
    ts.factory.createCallExpression(
      ts.factory.createPropertyAccessExpression(input, "map"),
      undefined,
      [arrow],
    );
}
