import type { AccessControlAction } from 'app/types/accessControl';

// Actions for Grafana-managed alert rules.
// Covers both global/list-level checks (Create, ExportRules) and per-rule instance checks
// (Duplicate, Silence, Pause, etc.). Hooks that operate at the global level set per-instance
// actions to NotSupported; hooks that operate on a single rule set Create to NotSupported.
export enum RuleAction {
  // global (list / page level)
  Create = 'create-alert-rule',
  ExportRules = 'export-grafana-managed-rules',

  // per-rule only
  View = 'view-alert-rule',
  Update = 'update-alert-rule',
  Delete = 'delete-alert-rule',
  Duplicate = 'duplicate-alert-rule',
  Explore = 'explore-alert-rule',
  Silence = 'silence-alert-rule',
  ModifyExport = 'modify-export-rule',
  Pause = 'pause-alert-rule',
  Restore = 'restore-alert-rule',
  DeletePermanently = 'delete-alert-rule-permanently',
  /**
   * Bulk-import external datasource-managed rules into Grafana-managed alerting.
   * Requires BOTH AlertingRuleCreate AND AlertingProvisioningSetStatus.
   */
  Import = 'import-alert-rule',
}

// Actions for external datasource alert rules (global / list level checks)
export enum ExternalRuleAction {
  CreateAlertRule = 'create-external-alert-rule',
  ViewAlertRule = 'view-external-alert-rule',
  UpdateAlertRule = 'update-external-alert-rule',
  DeleteAlertRule = 'delete-external-alert-rule',
}

export enum ContactPointAction {
  Create = 'create-contact-point',
  View = 'view-contact-point',
  Update = 'edit-contact-points',
  Delete = 'delete-contact-point',
  Export = 'export-contact-point',
  BulkExport = 'bulk-export-contact-points',
  Test = 'test-contact-point',
}

export enum NotificationTemplateAction {
  Create = 'create-notification-template',
  View = 'view-notification-template',
  Update = 'edit-notification-template',
  Delete = 'delete-notification-template',
  Test = 'test-notification-template',
}

export enum NotificationPolicyAction {
  Create = 'create-notification-policy',
  ViewTree = 'view-notification-policy-tree',
  UpdateTree = 'update-notification-policy-tree',
  Delete = 'delete-notification-policy',
  Export = 'export-notification-policies',
  ViewAutogenerated = 'view-autogenerated-policy-tree',
}

export enum SilenceAction {
  View = 'view-silence',
  Create = 'create-silence',
  Update = 'update-silence',
  Preview = 'preview-silenced-alerts',
}

export enum TimeIntervalAction {
  View = 'view-time-interval',
  Create = 'create-time-interval',
  Update = 'update-time-interval',
  Delete = 'delete-time-interval',
  Export = 'export-time-intervals',
}

export enum AlertGroupAction {
  View = 'view-alert-groups',
}

/** Cross-entity alertmanager actions that don't belong to a single entity. */
export enum AlertmanagerAdminAction {
  DecryptSecrets = 'decrypt-secrets',
}

/** Union of all alertmanager entity action enums. */
type AlertmanagerAction =
  | ContactPointAction
  | NotificationTemplateAction
  | NotificationPolicyAction
  | SilenceAction
  | TimeIntervalAction
  | AlertGroupAction
  | AlertmanagerAdminAction;

// Actions on the raw external alertmanager configuration (the JSON config blob).
export enum ExternalAlertmanagerAction {
  ViewExternalConfiguration = 'view-external-configuration',
  UpdateExternalConfiguration = 'update-external-configuration',
}

// this enum lists all of the bulk actions we can perform on a folder
export enum FolderBulkAction {
  Pause = 'pause-folder', // unpause permissions are the same as pause
  Delete = 'delete-folder',
}

// this enum lists all of the available actions we can perform with enrichments
export enum EnrichmentAction {
  Read = 'read-enrichment',
  Write = 'write-enrichment',
}

type Action =
  | AlertmanagerAction
  | ExternalAlertmanagerAction
  | RuleAction
  | ExternalRuleAction
  | FolderBulkAction
  | EnrichmentAction;

// ── Ability ─────────────────────────────────────────────────────────────

/**
 * Discriminated union representing the **synchronous** outcome of an ability check.
 *
 * When `granted` is `true` the action can be performed — no `cause` is present.
 * When `granted` is `false` the `cause` explains why:
 *
 * - `NOT_SUPPORTED`             — the feature or action doesn't exist in this context
 *                                 (wrong alertmanager type, disabled feature flag, wrong rule type)
 * - `PROVISIONED`               — the resource is read-only because it is provisioned
 *                                 (via Terraform, Ansible, or the provisioning API)
 * - `IS_PLUGIN_MANAGED`         — the resource is owned by an installed plugin and cannot be
 *                                 mutated through the Grafana UI
 * - `INSUFFICIENT_PERMISSIONS`  — the user lacks the required RBAC permissions;
 *                                 `anyOfPermissions` lists the permissions that would grant access
 *                                 (user needs **any one** of them)
 *
 * Hooks that return `Ability` are **always resolved** — callers never need to handle a loading
 * state. Use {@link AsyncAbility} for hooks that depend on async data (folder metadata, plugin
 * settings, alertmanager configuration).
 *
 * @example
 * // Sync hook — check granted directly, no loading guard needed
 * const ability = useGlobalRuleAbility(RuleAction.Create);
 * if (ability.granted) { ... }
 *
 * @example
 * // Rich UI using utility functions
 * const ability = useContactPointAbility({ action: ContactPointAction.Export });
 * {isSupported(ability) && (
 *   <Tooltip content={isInsufficientPermissions(ability) ? `Requires: ${ability.anyOfPermissions.join(', ')}` : undefined}>
 *     <Button disabled={!ability.granted}>Export</Button>
 *   </Tooltip>
 * )}
 */
export type Ability =
  | { granted: true }
  | { granted: false; cause: 'NOT_SUPPORTED' }
  | { granted: false; cause: 'PROVISIONED' }
  | { granted: false; cause: 'IS_PLUGIN_MANAGED' }
  | { granted: false; cause: 'INSUFFICIENT_PERMISSIONS'; anyOfPermissions: AccessControlAction[] }
  | { granted: false; cause: 'IN_USE'; blockedBy: Array<'routes' | 'rules'> };

/**
 * Extends {@link Ability} with a `LOADING` variant for hooks that depend on async data
 * (folder metadata, plugin settings, alertmanager configuration).
 *
 * Hooks returning `AsyncAbility` **may** be in a loading state — callers should guard with
 * {@link isLoading} before acting on the result.
 *
 * - `LOADING` — async checks are still in flight; re-evaluate once loading is complete
 *
 * @example
 * // Async hook — must handle loading before acting
 * const silenceAbility = useRuleSilenceAbility(rule);
 * if (isLoading(silenceAbility)) { return <Spinner />; }
 * if (silenceAbility.granted) { ... }
 */
export type AsyncAbility = Ability | { granted: false; cause: 'LOADING' };

/** Map of every action in domain T to its {@link Ability}. */
export type Abilities<T extends Action> = Record<T, Ability>;

// ── Sentinel constants ────────────────────────────────────────────────────────

/** The action is permitted. */
export const Granted: Ability = { granted: true };

/** Async checks are still in flight — re-evaluate once loading is complete. */
export const Loading: AsyncAbility = { granted: false, cause: 'LOADING' };

/**
 * The action is not applicable in the current context.
 * Use for wrong alertmanager type, disabled feature flags, or wrong rule type.
 * UI convention: hide the button entirely.
 */
export const NotSupported: Ability = { granted: false, cause: 'NOT_SUPPORTED' };

/**
 * The resource cannot be mutated because it is provisioned (Terraform, Ansible, provisioning API).
 * UI convention: show the button disabled with a tooltip explaining it is provisioned.
 */
export const Provisioned: Ability = { granted: false, cause: 'PROVISIONED' };

/**
 * The resource is owned by an installed plugin and cannot be mutated through the Grafana UI.
 * UI convention: show the button disabled with a tooltip naming the owning plugin.
 */
export const IsPluginManaged: Ability = { granted: false, cause: 'IS_PLUGIN_MANAGED' };

/**
 * The user lacks the RBAC permissions required to perform the action.
 * `anyOfPermissions` lists every permission that would grant access — the user needs any one.
 * UI convention: show the button disabled with a tooltip listing the required permissions.
 */
export function InsufficientPermissions(anyOfPermissions: AccessControlAction[]): Ability {
  return { granted: false, cause: 'INSUFFICIENT_PERMISSIONS', anyOfPermissions };
}

/**
 * The entity cannot be deleted because it is currently referenced by one or more routes or rules.
 * `blockedBy` lists which entity types are holding a reference.
 * UI convention: show the button disabled with a tooltip explaining which references must be removed first.
 */
export function InUse(blockedBy: Extract<Ability, { cause: 'IN_USE' }>['blockedBy']): Ability {
  return { granted: false, cause: 'IN_USE', blockedBy };
}

// ── Type-guard functions ──────────────────────────────────────────────────────

/** Narrows to the `IN_USE` variant. When `true`, TypeScript knows `ability.blockedBy` is present. */
export function isInUse(ability: AsyncAbility): ability is Extract<Ability, { cause: 'IN_USE' }> {
  return !ability.granted && ability.cause === 'IN_USE';
}

/**
 * Narrows to the `INSUFFICIENT_PERMISSIONS` variant.
 * When `true`, TypeScript knows `ability.anyOfPermissions` is present.
 *
 * @example
 * if (isInsufficientPermissions(ability)) {
 *   return <Tooltip content={`Requires any of: ${ability.anyOfPermissions.join(', ')}`}><Button disabled /></Tooltip>;
 * }
 */
export function isInsufficientPermissions(
  ability: AsyncAbility
): ability is { granted: false; cause: 'INSUFFICIENT_PERMISSIONS'; anyOfPermissions: AccessControlAction[] } {
  return !ability.granted && ability.cause === 'INSUFFICIENT_PERMISSIONS';
}
