import ts from "typescript";

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

import { MetadataObject } from "../../schemas/metadata/MetadataObject";

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

export namespace PruneJoiner {
  export const object = (
    input: ts.Expression,
    entries: IExpressionEntry[],
    obj: MetadataObject,
  ): ts.ConciseBody => {
    // PREPARE ASSETS
    const regular = entries.filter((entry) => entry.key.isSoleLiteral());
    const dynamic = entries.filter((entry) => !entry.key.isSoleLiteral());

    const statements: ts.Statement[] = regular
      .map((entry) =>
        ts.isBlock(entry.expression)
          ? [...entry.expression.statements]
          : [ts.factory.createExpressionStatement(entry.expression)],
      )
      .flat();
    if (dynamic.length)
      statements.push(
        ts.factory.createExpressionStatement(
          iterate_dynamic_properties({ regular, dynamic })(input),
        ),
      );

    statements.push(prune_object_properties(obj));
    return ts.factory.createBlock(statements, true);
  };

  export const array = (input: ts.Expression, arrow: ts.ArrowFunction) =>
    ts.factory.createCallExpression(
      IdentifierFactory.access(input)("forEach"),
      undefined,
      [arrow],
    );

  export const tuple = (
    children: ts.ConciseBody[],
    rest: ts.ConciseBody | null,
  ): ts.Block => {
    const entire: ts.ConciseBody[] = [...children];
    if (rest !== null) entire.push(rest);

    const statements: ts.Statement[] = entire
      .map((elem) =>
        ts.isBlock(elem)
          ? [...elem.statements]
          : [ts.factory.createExpressionStatement(elem)],
      )
      .flat();
    return ts.factory.createBlock(statements, true);
  };
}

const iterate_dynamic_properties =
  (props: { regular: IExpressionEntry[]; dynamic: IExpressionEntry[] }) =>
  (input: ts.Expression) =>
    ts.factory.createCallExpression(
      IdentifierFactory.access(
        ts.factory.createCallExpression(
          ts.factory.createIdentifier("Object.entries"),
          undefined,
          [input],
        ),
      )("forEach"),
      undefined,
      [
        ts.factory.createArrowFunction(
          undefined,
          undefined,
          [
            IdentifierFactory.parameter(
              ts.factory.createArrayBindingPattern(
                ["key", "value"].map((l) =>
                  ts.factory.createBindingElement(
                    undefined,
                    undefined,
                    ts.factory.createIdentifier(l),
                    undefined,
                  ),
                ),
              ),
            ),
          ],
          undefined,
          undefined,
          ts.factory.createBlock(
            [
              ts.factory.createIfStatement(
                ts.factory.createStrictEquality(
                  ts.factory.createIdentifier("undefined"),
                  ts.factory.createIdentifier("value"),
                ),
                ts.factory.createReturnStatement(),
              ),
              ...props.regular.map(({ key }) =>
                ts.factory.createIfStatement(
                  ts.factory.createStrictEquality(
                    ts.factory.createStringLiteral(key.getSoleLiteral()!),
                    ts.factory.createIdentifier("key"),
                  ),
                  ts.factory.createReturnStatement(),
                ),
              ),
              ...props.dynamic.map((dynamic) =>
                ts.factory.createIfStatement(
                  ts.factory.createCallExpression(
                    ts.factory.createIdentifier(
                      `RegExp(/${metadata_to_pattern(true)(
                        dynamic.key,
                      )}/).test`,
                    ),
                    undefined,
                    [ts.factory.createIdentifier("key")],
                  ),
                  ts.isBlock(dynamic.expression)
                    ? dynamic.expression
                    : ts.factory.createBlock([
                        ts.factory.createExpressionStatement(
                          dynamic.expression,
                        ),
                      ]),
                ),
              ),
            ],
            true,
          ),
        ),
      ],
    );
