import { query as q } from 'good-enough-parser';
import { z } from 'zod';
import { regEx } from '../../../../util/regex';
import { MavenDatasource } from '../../../datasource/maven';
import { id as versioning } from '../../../versioning/gradle';
import type { PackageDependency } from '../../types';
import type { Ctx } from '../context';
import {
  RecordFragmentSchema,
  StringArrayFragmentSchema,
  StringFragmentSchema,
} from '../fragments';
import { kvParams } from './common';

const artifactMethod = 'artifact';
const installMethod = 'install';
const commonDepType = 'maven_install';
const mavenVariableRegex = regEx(/^maven.*/);
const bzlmodMavenMethods = [installMethod, artifactMethod];
const methodRegex = regEx(`^${bzlmodMavenMethods.join('|')}$`);

function getParsedRuleByMethod(method: string): string {
  return `maven_${method}`;
}

const ArtifactSpec = z.object({
  group: z.string(),
  artifact: z.string(),
  version: z.string(),
});
type ArtifactSpec = z.infer<typeof ArtifactSpec>;

const MavenArtifactTarget = RecordFragmentSchema.extend({
  children: z.object({
    rule: StringFragmentSchema.extend({
      value: z.literal(getParsedRuleByMethod(artifactMethod)),
    }),
    artifact: StringFragmentSchema,
    group: StringFragmentSchema,
    version: StringFragmentSchema,
  }),
}).transform(
  ({ children: { rule, artifact, group, version } }): PackageDependency[] => [
    {
      datasource: MavenDatasource.id,
      versioning,
      depName: `${group.value}:${artifact.value}`,
      currentValue: version.value,
      depType: rule.value,
    },
  ],
);

const MavenInstallTarget = RecordFragmentSchema.extend({
  children: z.object({
    rule: StringFragmentSchema.extend({
      value: z.literal(getParsedRuleByMethod(installMethod)),
    }),
    artifacts: StringArrayFragmentSchema.transform((artifacts) => {
      const result: ArtifactSpec[] = [];
      for (const { value } of artifacts.items) {
        const [group, artifact, version] = value.split(':');
        if (group && artifact && version) {
          result.push({ group, artifact, version });
        }
      }

      return result;
    }),
    repositories: StringArrayFragmentSchema,
  }),
}).transform(
  ({ children: { rule, artifacts, repositories } }): PackageDependency[] =>
    artifacts.map(({ group, artifact, version: currentValue }) => ({
      datasource: MavenDatasource.id,
      versioning,
      depName: `${group}:${artifact}`,
      currentValue,
      depType: rule.value,
      registryUrls: repositories.items.map((i) => i.value),
    })),
);

export const RuleToMavenPackageDep = z.union([
  MavenArtifactTarget,
  MavenInstallTarget,
]);

export function fillRegistryUrls(
  packageDeps: PackageDependency[][],
): PackageDependency[] {
  const artifactRules: PackageDependency[] = [];
  const registryUrls: string[] = [];
  const result: PackageDependency[] = [];

  // registry urls are specified only in maven.install, not in maven.artifact
  packageDeps.flat().forEach((dep) => {
    if (dep.depType === getParsedRuleByMethod(installMethod)) {
      if (Array.isArray(dep.registryUrls)) {
        registryUrls.push(...dep.registryUrls);
        result.push(dep);
      }
    } else if (dep.depType === getParsedRuleByMethod(artifactMethod)) {
      artifactRules.push(dep);
    }
  });

  const uniqUrls = [...new Set(registryUrls)];

  for (const artifactRule of artifactRules) {
    artifactRule.registryUrls = uniqUrls;
    artifactRule.depType = commonDepType;
    result.push(artifactRule);
  }

  return result;
}

export const mavenRules = q
  .sym<Ctx>(mavenVariableRegex, (ctx, token) => {
    return ctx.startRule(token.value);
  })
  .op('.')
  .sym(methodRegex, (ctx, token) => {
    const rule = ctx.currentRecord.children.rule;
    if (rule.type === 'string') {
      rule.value = getParsedRuleByMethod(token.value);
    }
    return ctx;
  })
  .join(
    q.tree({
      type: 'wrapped-tree',
      maxDepth: 1,
      search: kvParams,
      postHandler: (ctx) => ctx.endRule(),
    }),
  );