import type { ActionDefinition, JsonSchema } from "../../core/types.ts";

import { s } from "../../core/json-schema.ts";
import { defineProviderAction } from "../../core/provider-definition.ts";

const service = "launch_darkly";

const looseObject = s.looseObject({}, { description: "A LaunchDarkly JSON object." });
const looseObjectList = s.array(looseObject, { description: "A list of LaunchDarkly JSON objects." });
const emptyOutput = s.looseObject({}, { description: "An empty LaunchDarkly response body." });
const stringList = (description: string): JsonSchema => s.stringArray(description, { minItems: 1 });
const nonEmptyString = (description: string): JsonSchema => s.nonEmptyString(description);
const optionalText = (description: string): JsonSchema => s.string({ description });
const integer = (description: string, minimum = 0): JsonSchema => s.integer({ minimum, description });
const limit = integer("The maximum number of items to return.", 1);
const offset = integer("The number of items to skip before returning results.");
const filter = optionalText("The LaunchDarkly filter expression to apply to the result set.");
const sort = optionalText("The LaunchDarkly sort expression to apply to the result set.");
const expand = optionalText("A comma-separated list of related collections to expand in the response.");
const continuationToken = optionalText("The continuation token returned by a previous paginated response.");
const projectKey = nonEmptyString("The LaunchDarkly project key.");
const environmentKey = nonEmptyString("The LaunchDarkly environment key.");
const featureFlagKey = nonEmptyString("The LaunchDarkly feature flag key.");
const segmentKey = nonEmptyString("The LaunchDarkly segment key.");
const memberId = nonEmptyString("The LaunchDarkly member identifier.");
const teamKey = nonEmptyString("The LaunchDarkly team key.");
const tokenId = nonEmptyString("The LaunchDarkly access token identifier.");

const patchOperation = s.object(
  {
    op: s.stringEnum(["add", "remove", "replace", "move", "copy", "test"], {
      description: "The JSON Patch operation.",
    }),
    path: nonEmptyString("The JSON Pointer path that the patch operation targets."),
    from: s.string({ description: "The source JSON Pointer path for move or copy operations." }),
    value: s.unknown("The value used by the patch operation."),
  },
  {
    required: ["op", "path"],
    description: "A single JSON Patch operation.",
  },
);
const patchOperations = s.array(patchOperation, {
  minItems: 1,
  description: "JSON Patch operations to apply.",
});
const semanticInstruction = s.looseObject(
  {
    kind: nonEmptyString("The semantic patch instruction kind."),
  },
  { description: "A semantic patch instruction accepted by LaunchDarkly." },
);
const semanticInstructions = s.array(semanticInstruction, {
  minItems: 1,
  description: "Semantic patch instructions to apply.",
});
const collection = (item: JsonSchema, description: string): JsonSchema =>
  s.looseObject(
    {
      items: s.array(item, { description: "The collection items returned by LaunchDarkly." }),
      totalCount: s.integer({ description: "The total number of matching items returned by LaunchDarkly." }),
      continuationToken: s.nullable(s.string({ description: "The continuation token for the next page." })),
      _links: looseObject,
    },
    { description },
  );

const callerIdentity = s.looseObject(
  {
    accountId: s.nullable(s.string({ description: "The LaunchDarkly account identifier for the caller." })),
    accountName: s.nullable(s.string({ description: "The LaunchDarkly account name for the caller." })),
    tokenId: s.nullable(s.string({ description: "The access token identifier for the caller." })),
    tokenName: s.nullable(s.string({ description: "The access token name for the caller." })),
    memberId: s.nullable(s.string({ description: "The LaunchDarkly member identifier for the caller." })),
    scopes: s.stringArray("The scopes or permission strings returned for the caller."),
  },
  { description: "The LaunchDarkly caller identity for the provided access token." },
);
const project = s.looseObject(
  {
    _id: s.string({ description: "The LaunchDarkly project identifier." }),
    key: s.string({ description: "The LaunchDarkly project key." }),
    name: s.string({ description: "The LaunchDarkly project name." }),
    description: s.nullable(s.string({ description: "The LaunchDarkly project description." })),
    tags: s.stringArray("The tags assigned to the project."),
    environments: looseObject,
    _links: looseObject,
  },
  { description: "A LaunchDarkly project." },
);
const environment = s.looseObject(
  {
    _id: s.string({ description: "The LaunchDarkly environment identifier." }),
    key: s.string({ description: "The LaunchDarkly environment key." }),
    name: s.string({ description: "The LaunchDarkly environment name." }),
    color: s.nullable(s.string({ description: "The display color configured for the environment." })),
    tags: s.stringArray("The tags assigned to the environment."),
    _links: looseObject,
  },
  { description: "A LaunchDarkly environment." },
);
const featureFlag = s.looseObject(
  {
    _id: s.string({ description: "The LaunchDarkly feature flag identifier." }),
    key: s.string({ description: "The LaunchDarkly feature flag key." }),
    name: s.string({ description: "The LaunchDarkly feature flag name." }),
    kind: s.string({ description: "The LaunchDarkly feature flag kind." }),
    description: s.nullable(s.string({ description: "The LaunchDarkly feature flag description." })),
    archived: s.boolean({ description: "Whether the feature flag is archived." }),
    tags: s.stringArray("The tags assigned to the feature flag."),
    environments: looseObject,
    _links: looseObject,
  },
  { description: "A LaunchDarkly feature flag." },
);
const segment = s.looseObject(
  {
    _id: s.string({ description: "The LaunchDarkly segment identifier." }),
    key: s.string({ description: "The LaunchDarkly segment key." }),
    name: s.string({ description: "The LaunchDarkly segment name." }),
    description: s.nullable(s.string({ description: "The LaunchDarkly segment description." })),
    tags: s.stringArray("The tags assigned to the segment."),
    included: s.stringArray("The included context keys configured on the segment."),
    excluded: s.stringArray("The excluded context keys configured on the segment."),
    rules: looseObjectList,
    _links: looseObject,
  },
  { description: "A LaunchDarkly segment." },
);
const contextItem = s.looseObject(
  {
    context: s.union([s.string({ description: "The context string." }), looseObject], {
      description: "The context payload returned by LaunchDarkly.",
    }),
    lastSeen: s.nullable(s.string({ description: "The timestamp when LaunchDarkly last saw the context." })),
    associatedContexts: s.integer({ description: "The number of associated contexts returned for the item." }),
    _links: looseObject,
  },
  { description: "A LaunchDarkly context item." },
);
const member = s.looseObject(
  {
    _id: s.string({ description: "The LaunchDarkly member identifier." }),
    email: s.email("The LaunchDarkly member email address."),
    firstName: s.nullable(s.string({ description: "The LaunchDarkly member first name." })),
    lastName: s.nullable(s.string({ description: "The LaunchDarkly member last name." })),
    role: s.nullable(s.string({ description: "The LaunchDarkly member role." })),
    customRoles: s.stringArray("The custom roles assigned to the member."),
    _links: looseObject,
  },
  { description: "A LaunchDarkly account member." },
);
const team = s.looseObject(
  {
    _id: s.string({ description: "The LaunchDarkly team identifier." }),
    key: s.string({ description: "The LaunchDarkly team key." }),
    name: s.string({ description: "The LaunchDarkly team name." }),
    description: s.nullable(s.string({ description: "The LaunchDarkly team description." })),
    customRoleKeys: s.stringArray("The custom role keys assigned to the team."),
    maintainerIds: s.stringArray("The member identifiers assigned as maintainers for the team."),
    memberIds: s.stringArray("The member identifiers assigned to the team."),
    _links: looseObject,
  },
  { description: "A LaunchDarkly team." },
);
const token = s.looseObject(
  {
    _id: s.string({ description: "The LaunchDarkly access token identifier." }),
    name: s.string({ description: "The LaunchDarkly access token name." }),
    description: s.nullable(s.string({ description: "The LaunchDarkly access token description." })),
    role: s.nullable(s.string({ description: "The base role assigned to the token." })),
    token: s.nullable(s.string({ description: "The token secret value, when returned by LaunchDarkly." })),
    customRoleIds: s.stringArray("The custom role identifiers assigned to the token."),
    inlineRole: looseObject,
    _links: looseObject,
  },
  { description: "A LaunchDarkly access token." },
);

export type LaunchDarklyActionName =
  | "get_caller_identity"
  | "list_projects"
  | "get_project"
  | "create_project"
  | "patch_project"
  | "delete_project"
  | "get_environments"
  | "get_environment"
  | "create_environment"
  | "patch_environment"
  | "delete_environment"
  | "get_feature_flags"
  | "get_feature_flag"
  | "create_feature_flag"
  | "patch_feature_flag"
  | "delete_feature_flag"
  | "get_segments"
  | "get_segment"
  | "create_segment"
  | "patch_segment"
  | "delete_segment"
  | "search_contexts"
  | "get_contexts"
  | "search_context_instances"
  | "get_context_instances"
  | "get_members"
  | "get_member"
  | "list_teams"
  | "get_team"
  | "create_team"
  | "patch_team"
  | "delete_team"
  | "add_member_to_teams"
  | "get_tags"
  | "get_tokens"
  | "get_token"
  | "create_token"
  | "patch_token"
  | "delete_token"
  | "reset_token";

export const launchDarklyActions: ActionDefinition[] = [
  action(
    "get_caller_identity",
    "Get the LaunchDarkly caller identity for the current access token.",
    {},
    [],
    callerIdentity,
  ),
  action(
    "list_projects",
    "List LaunchDarkly projects with optional filtering, sorting, pagination, and expansion.",
    listInput({ expand }),
    [],
    collection(project, "A LaunchDarkly project collection response."),
  ),
  action("get_project", "Get a LaunchDarkly project by project key.", { projectKey, expand }, ["projectKey"], project),
  action(
    "create_project",
    "Create a LaunchDarkly project using either common fields or a full official request body.",
    {
      key: nonEmptyString("The project key to create."),
      name: nonEmptyString("The project name to create."),
      description: optionalText("The project description."),
      includeInSnippetByDefault: s.boolean({
        description: "Whether new flags should be included in snippets by default.",
      }),
      tags: stringList("The tags to assign to the project."),
      defaultClientSideAvailability: looseObject,
      body: looseObject,
    },
    [],
    project,
    { anyOf: [{ required: ["body"] }, { required: ["key", "name"] }] },
  ),
  action(
    "patch_project",
    "Patch a LaunchDarkly project with standard JSON Patch operations.",
    { projectKey, patch: patchOperations },
    ["projectKey", "patch"],
    project,
  ),
  action(
    "delete_project",
    "Delete a LaunchDarkly project by project key.",
    { projectKey },
    ["projectKey"],
    emptyOutput,
  ),
  action(
    "get_environments",
    "List the LaunchDarkly environments that belong to a project.",
    { projectKey },
    ["projectKey"],
    collection(environment, "A LaunchDarkly environment collection response."),
  ),
  action(
    "get_environment",
    "Get a LaunchDarkly environment by project key and environment key.",
    { projectKey, environmentKey },
    ["projectKey", "environmentKey"],
    environment,
  ),
  action(
    "create_environment",
    "Create a LaunchDarkly environment using either common fields or a full official request body.",
    {
      projectKey,
      key: nonEmptyString("The environment key to create."),
      name: nonEmptyString("The environment name to create."),
      color: optionalText("The display color for the environment."),
      defaultTtl: integer("The default time-to-live value for temporary targeting, in minutes."),
      secureMode: s.boolean({ description: "Whether secure mode should be enabled for the environment." }),
      defaultTrackEvents: s.boolean({ description: "Whether event tracking should be enabled by default." }),
      confirmChanges: s.boolean({ description: "Whether UI changes should require confirmation." }),
      requireComments: s.boolean({ description: "Whether UI changes should require comments." }),
      tags: stringList("The tags to assign to the environment."),
      approvalSettings: looseObject,
      defaults: looseObject,
      body: looseObject,
    },
    ["projectKey"],
    environment,
    { anyOf: [{ required: ["body"] }, { required: ["key", "name"] }] },
  ),
  action(
    "patch_environment",
    "Patch a LaunchDarkly environment with standard JSON Patch operations.",
    { projectKey, environmentKey, patch: patchOperations },
    ["projectKey", "environmentKey", "patch"],
    environment,
  ),
  action(
    "delete_environment",
    "Delete a LaunchDarkly environment by project key and environment key.",
    { projectKey, environmentKey },
    ["projectKey", "environmentKey"],
    emptyOutput,
  ),
  action(
    "get_feature_flags",
    "List LaunchDarkly feature flags in a project with optional filtering, pagination, and summary output.",
    {
      projectKey,
      ...listInput(),
      summary: s.boolean({ description: "Whether to request summary output for each feature flag." }),
      env: optionalText("The environment key used to filter the response."),
      tag: optionalText("A tag used to filter the response."),
      archived: s.boolean({ description: "Whether to include archived flags." }),
    },
    ["projectKey"],
    collection(featureFlag, "A LaunchDarkly feature flag collection response."),
  ),
  action(
    "get_feature_flag",
    "Get a LaunchDarkly feature flag by project key and feature flag key.",
    {
      projectKey,
      featureFlagKey,
      env: optionalText("The environment key used to scope the feature flag response."),
      summary: s.boolean({ description: "Whether to request a summary response." }),
    },
    ["projectKey", "featureFlagKey"],
    featureFlag,
  ),
  action(
    "create_feature_flag",
    "Create a LaunchDarkly feature flag using either common fields or a full official request body.",
    {
      projectKey,
      key: nonEmptyString("The feature flag key to create."),
      name: nonEmptyString("The feature flag name to create."),
      kind: s.stringEnum(["boolean", "multivariate"], { description: "The LaunchDarkly feature flag kind." }),
      description: optionalText("The feature flag description."),
      temporary: s.boolean({ description: "Whether the feature flag is temporary." }),
      tags: stringList("The tags to assign to the feature flag."),
      clientSideAvailability: looseObject,
      variations: looseObjectList,
      defaults: looseObject,
      body: looseObject,
    },
    ["projectKey"],
    featureFlag,
    { anyOf: [{ required: ["body"] }, { required: ["key", "name", "kind"] }] },
  ),
  patchModeAction(
    "patch_feature_flag",
    "Patch a LaunchDarkly feature flag with JSON Patch, JSON Merge Patch, or semantic patch instructions.",
    {
      projectKey,
      featureFlagKey,
      environmentKey,
      dryRun: s.boolean({ description: "Whether to validate the patch without applying it." }),
      ignoreConflicts: s.boolean({ description: "Whether to ignore conflicts during patch execution." }),
    },
    ["projectKey", "featureFlagKey"],
    featureFlag,
  ),
  action(
    "delete_feature_flag",
    "Delete a LaunchDarkly feature flag by project key and feature flag key.",
    { projectKey, featureFlagKey },
    ["projectKey", "featureFlagKey"],
    emptyOutput,
  ),
  action(
    "get_segments",
    "List LaunchDarkly segments in a project environment with optional filtering and pagination.",
    { projectKey, environmentKey, ...listInput() },
    ["projectKey", "environmentKey"],
    collection(segment, "A LaunchDarkly segment collection response."),
  ),
  action(
    "get_segment",
    "Get a LaunchDarkly segment by project key, environment key, and segment key.",
    { projectKey, environmentKey, segmentKey },
    ["projectKey", "environmentKey", "segmentKey"],
    segment,
  ),
  action(
    "create_segment",
    "Create a LaunchDarkly segment using either common fields or a full official request body.",
    {
      projectKey,
      environmentKey,
      key: nonEmptyString("The segment key to create."),
      name: nonEmptyString("The segment name to create."),
      description: optionalText("The segment description."),
      tags: stringList("The tags to assign to the segment."),
      included: stringList("The context keys to include in the segment."),
      excluded: stringList("The context keys to exclude from the segment."),
      rules: looseObjectList,
      unbounded: s.boolean({ description: "Whether the segment should be configured as unbounded." }),
      unboundedContextKind: optionalText("The context kind used for unbounded segment membership."),
      body: looseObject,
    },
    ["projectKey", "environmentKey"],
    segment,
    { anyOf: [{ required: ["body"] }, { required: ["key", "name"] }] },
  ),
  patchModeAction(
    "patch_segment",
    "Patch a LaunchDarkly segment with JSON Patch, JSON Merge Patch, or semantic patch instructions.",
    { projectKey, environmentKey, segmentKey },
    ["projectKey", "environmentKey", "segmentKey"],
    segment,
  ),
  action(
    "delete_segment",
    "Delete a LaunchDarkly segment by project key, environment key, and segment key.",
    { projectKey, environmentKey, segmentKey },
    ["projectKey", "environmentKey", "segmentKey"],
    emptyOutput,
  ),
  action(
    "search_contexts",
    "Search LaunchDarkly contexts in a project environment with filtering, sorting, and pagination.",
    contextSearchInput(),
    ["projectKey", "environmentKey"],
    collection(contextItem, "A LaunchDarkly context collection response."),
  ),
  action(
    "get_contexts",
    "Get a LaunchDarkly context by context kind and key, with optional paging over related results.",
    {
      ...contextSearchInput(),
      kind: nonEmptyString("The LaunchDarkly context kind."),
      key: nonEmptyString("The LaunchDarkly context key."),
    },
    ["projectKey", "environmentKey", "kind", "key"],
    collection(contextItem, "A LaunchDarkly context collection response."),
  ),
  action(
    "search_context_instances",
    "Search LaunchDarkly context instances in a project environment with filtering, sorting, and pagination.",
    contextSearchInput(),
    ["projectKey", "environmentKey"],
    collection(contextItem, "A LaunchDarkly context collection response."),
  ),
  action(
    "get_context_instances",
    "Get a LaunchDarkly context instance by project key, environment key, and context instance identifier.",
    { ...contextSearchInput(), contextInstanceId: nonEmptyString("The LaunchDarkly context instance identifier.") },
    ["projectKey", "environmentKey", "contextInstanceId"],
    collection(contextItem, "A LaunchDarkly context collection response."),
  ),
  action(
    "get_members",
    "List LaunchDarkly account members with optional filtering, sorting, pagination, and expansion.",
    listInput({ expand }),
    [],
    collection(member, "A LaunchDarkly member collection response."),
  ),
  action(
    "get_member",
    "Get a LaunchDarkly account member by member identifier.",
    { memberId, expand },
    ["memberId"],
    member,
  ),
  action(
    "list_teams",
    "List LaunchDarkly teams with optional filtering, pagination, and expansion controls.",
    listInput({ expand }),
    [],
    collection(team, "A LaunchDarkly team collection response."),
  ),
  action("get_team", "Get a LaunchDarkly team by team key.", { teamKey, expand }, ["teamKey"], team),
  action(
    "create_team",
    "Create a LaunchDarkly team using either common fields or a full official request body.",
    {
      key: nonEmptyString("The team key to create."),
      name: nonEmptyString("The team name to create."),
      description: optionalText("The team description."),
      customRoleKeys: stringList("The custom role keys to assign to the team."),
      memberIds: stringList("The member identifiers to assign to the team."),
      maintainerIds: stringList("The member identifiers to assign as team maintainers."),
      roleAttributes: looseObject,
      body: looseObject,
    },
    [],
    team,
    { anyOf: [{ required: ["body"] }, { required: ["key", "name"] }] },
  ),
  action(
    "patch_team",
    "Patch a LaunchDarkly team with semantic patch instructions.",
    {
      teamKey,
      expand,
      comment: optionalText("The comment to attach to the update."),
      instructions: semanticInstructions,
    },
    ["teamKey", "instructions"],
    team,
  ),
  action("delete_team", "Delete a LaunchDarkly team by team key.", { teamKey }, ["teamKey"], emptyOutput),
  action(
    "add_member_to_teams",
    "Add one or more LaunchDarkly members to one or more teams with a semantic patch update.",
    {
      memberIds: s.array(memberId, {
        minItems: 1,
        description: "The LaunchDarkly member identifiers to add to teams.",
      }),
      teamKeys: s.array(teamKey, {
        minItems: 1,
        description: "The LaunchDarkly team keys that should receive the members.",
      }),
      comment: optionalText("The comment to attach to the update."),
    },
    ["memberIds", "teamKeys"],
    s.looseObject(
      {
        memberIDs: s.stringArray("The member identifiers that were processed by the update."),
        teamKeys: s.stringArray("The team keys that were processed by the update."),
        errors: looseObjectList,
      },
      { description: "The result of adding members to multiple LaunchDarkly teams." },
    ),
  ),
  action(
    "get_tags",
    "List LaunchDarkly tags with optional prefix and resource-kind filters.",
    {
      limit,
      offset,
      pre: optionalText("The tag prefix used to filter the result set."),
      kind: s.union(
        [
          s.string({ description: "A resource kind used to filter the result set." }),
          s.array(s.string({ description: "A resource kind used to filter the result set." }), { minItems: 1 }),
        ],
        { description: "The resource kinds used to filter the result set." },
      ),
    },
    [],
    collection(s.string({ description: "A LaunchDarkly tag." }), "A LaunchDarkly tag collection response."),
  ),
  action(
    "get_tokens",
    "List LaunchDarkly access tokens with optional pagination and visibility scope.",
    {
      limit,
      offset,
      showAll: s.boolean({ description: "Whether to include all access tokens visible to the caller." }),
    },
    [],
    collection(token, "A LaunchDarkly access token collection response."),
  ),
  action("get_token", "Get a LaunchDarkly access token by token identifier.", { tokenId }, ["tokenId"], token),
  action(
    "create_token",
    "Create a LaunchDarkly access token using either common fields or a full official request body.",
    {
      name: nonEmptyString("The access token name to create."),
      description: optionalText("The access token description."),
      role: optionalText("The base role assigned to the token."),
      customRoleIds: stringList("The custom role identifiers assigned to the token."),
      inlineRole: looseObject,
      serviceToken: s.boolean({ description: "Whether the token should be a service token." }),
      defaultApiVersion: s.integer({ description: "The default API version to associate with the token." }),
      body: looseObject,
    },
    [],
    token,
    {
      anyOf: [
        { required: ["body"] },
        {
          required: ["name"],
          anyOf: [{ required: ["role"] }, { required: ["customRoleIds"] }, { required: ["inlineRole"] }],
        },
      ],
    },
  ),
  action(
    "patch_token",
    "Patch a LaunchDarkly access token with standard JSON Patch operations.",
    { tokenId, patch: patchOperations },
    ["tokenId", "patch"],
    token,
  ),
  action(
    "delete_token",
    "Delete a LaunchDarkly access token by token identifier.",
    { tokenId },
    ["tokenId"],
    emptyOutput,
  ),
  action(
    "reset_token",
    "Reset a LaunchDarkly access token value and optionally control when the old value expires.",
    {
      tokenId,
      expiry: s.integer({
        description: "The Unix timestamp, in milliseconds, when the old token value should expire.",
      }),
    },
    ["tokenId"],
    token,
  ),
];

function action(
  name: LaunchDarklyActionName,
  description: string,
  input: Record<string, JsonSchema>,
  required: string[],
  output: JsonSchema,
  inputExtras: Record<string, unknown> = {},
): ActionDefinition {
  return defineProviderAction(service, {
    name,
    description,
    requiredScopes: [],
    inputSchema: {
      ...s.actionInput(input, required, "The input payload for this action."),
      ...inputExtras,
    },
    outputSchema: output,
  });
}

function patchModeAction(
  name: LaunchDarklyActionName,
  description: string,
  requiredInput: Record<string, JsonSchema>,
  required: string[],
  output: JsonSchema,
): ActionDefinition {
  return action(
    name,
    description,
    {
      ...requiredInput,
      patch: patchOperations,
      merge: s.looseObject({}, { description: "The JSON Merge Patch payload to apply." }),
      instructions: semanticInstructions,
      comment: optionalText("The comment to attach to the update, when supported."),
    },
    required,
    output,
    {
      oneOf: [
        { required: ["patch"] },
        { required: ["merge"], not: { required: ["comment"] } },
        { required: ["instructions"] },
      ],
    },
  );
}

function listInput(extra: Record<string, JsonSchema> = {}): Record<string, JsonSchema> {
  return {
    limit,
    offset,
    filter,
    sort,
    ...extra,
  };
}

function contextSearchInput(): Record<string, JsonSchema> {
  return {
    projectKey,
    environmentKey,
    filter,
    sort,
    limit,
    continuationToken,
  };
}
