// Slack plugin module implements blocks render behavior.
import type { Block, KnownBlock } from "@slack/web-api";
import { parseExecApprovalCommandText } from "openclaw/plugin-sdk/approval-reply-runtime";
import {
  reduceInteractiveReply,
  resolveMessagePresentationControlValue,
} from "openclaw/plugin-sdk/interactive-runtime";
import type {
  InteractiveReply,
  MessagePresentation,
  MessagePresentationButtonsBlock,
  MessagePresentationSelectBlock,
} from "openclaw/plugin-sdk/interactive-runtime";
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
import {
  SLACK_ACTION_BLOCK_ELEMENTS_MAX,
  SLACK_ACTION_LABEL_MAX,
  SLACK_BUTTON_VALUE_MAX,
  SLACK_HEADER_TEXT_MAX,
  SLACK_OPTION_VALUE_MAX,
  SLACK_SECTION_TEXT_MAX,
  SLACK_STATIC_SELECT_OPTIONS_MAX,
} from "./presentation.js";
import {
  SLACK_REPLY_BUTTON_ACTION_ID,
  SLACK_REPLY_LINK_ACTION_ID,
  SLACK_REPLY_SELECT_ACTION_ID,
} from "./reply-action-ids.js";
import { truncateSlackText } from "./truncate.js";

const SLACK_BUTTON_URL_MAX = 3000;

export type SlackBlock = Block | KnownBlock;

type SlackInteractiveBlockRenderOptions = {
  buttonIndexOffset?: number;
  selectIndexOffset?: number;
};

function buildSlackReplyButtonActionId(buttonIndex: number, choiceIndex: number): string {
  return `${SLACK_REPLY_BUTTON_ACTION_ID}:${String(buttonIndex)}:${String(choiceIndex + 1)}`;
}

function buildSlackReplyLinkActionId(buttonIndex: number, choiceIndex: number): string {
  return `${SLACK_REPLY_LINK_ACTION_ID}:${String(buttonIndex)}:${String(choiceIndex + 1)}`;
}

function buildSlackReplySelectActionId(selectIndex: number): string {
  return `${SLACK_REPLY_SELECT_ACTION_ID}:${String(selectIndex)}`;
}

function resolveSlackButtonStyle(
  style: "primary" | "secondary" | "success" | "danger" | undefined,
) {
  if (style === "primary" || style === "danger") {
    return style;
  }
  if (style === "success") {
    return "primary";
  }
  return undefined;
}

function resolveSlackControlValue(control: {
  action?: { type: "command"; command: string } | { type: "callback"; value: string };
  value?: string;
}): string | undefined {
  if (control.action?.type === "command") {
    const command = normalizeOptionalString(control.action.command);
    if (command && parseExecApprovalCommandText(command)) {
      return command;
    }
    const legacyValue = normalizeOptionalString(control.value);
    return legacyValue && parseExecApprovalCommandText(legacyValue) ? legacyValue : undefined;
  }
  return resolveMessagePresentationControlValue(control);
}

function isWithinSlackLimit(value: string, maxLength: number): boolean {
  return value.length <= maxLength;
}

function isRenderableSlackOption(option: {
  label: string;
  value: string | undefined;
}): option is { label: string; value: string } {
  return option.value !== undefined && isWithinSlackLimit(option.value, SLACK_OPTION_VALUE_MAX);
}

function readSlackBlockId(block: SlackBlock): string | undefined {
  const value = (block as { block_id?: unknown }).block_id;
  return typeof value === "string" ? value : undefined;
}

function readSlackOpenClawBlockIndex(blockId: string, prefix: string): number | undefined {
  if (!blockId.startsWith(prefix)) {
    return undefined;
  }
  const value = Number.parseInt(blockId.slice(prefix.length), 10);
  return Number.isSafeInteger(value) && value > 0 ? value : undefined;
}

/** Resolve existing OpenClaw Block Kit indexes so appended controls keep stable unique IDs. */
export function resolveSlackInteractiveBlockOffsets(
  blocks?: readonly SlackBlock[],
): SlackInteractiveBlockRenderOptions {
  let buttonIndexOffset = 0;
  let selectIndexOffset = 0;
  for (const block of blocks ?? []) {
    const blockId = readSlackBlockId(block);
    if (!blockId) {
      continue;
    }
    buttonIndexOffset = Math.max(
      buttonIndexOffset,
      readSlackOpenClawBlockIndex(blockId, "openclaw_reply_buttons_") ?? 0,
    );
    selectIndexOffset = Math.max(
      selectIndexOffset,
      readSlackOpenClawBlockIndex(blockId, "openclaw_reply_select_") ?? 0,
    );
  }
  return { buttonIndexOffset, selectIndexOffset };
}

/**
 * @deprecated Use buildSlackPresentationBlocks with MessagePresentation.
 */
export function buildSlackInteractiveBlocks(
  interactive?: InteractiveReply,
  options: SlackInteractiveBlockRenderOptions = {},
): SlackBlock[] {
  const initialState = {
    blocks: [] as SlackBlock[],
    buttonIndex: options.buttonIndexOffset ?? 0,
    selectIndex: options.selectIndexOffset ?? 0,
  };
  return reduceInteractiveReply(interactive, initialState, (state, block) => {
    if (block.type === "text") {
      const trimmed = block.text.trim();
      if (!trimmed) {
        return state;
      }
      state.blocks.push({
        type: "section",
        text: {
          type: "mrkdwn",
          text: truncateSlackText(trimmed, SLACK_SECTION_TEXT_MAX),
        },
      });
      return state;
    }
    if (block.type === "buttons") {
      const elements = block.buttons
        .flatMap((button, choiceIndex) => {
          const callbackData = resolveSlackControlValue(button);
          const value =
            callbackData && isWithinSlackLimit(callbackData, SLACK_BUTTON_VALUE_MAX)
              ? callbackData
              : undefined;
          const url =
            button.url && isWithinSlackLimit(button.url, SLACK_BUTTON_URL_MAX)
              ? button.url
              : undefined;
          if (!value && !url) {
            return [];
          }
          const target = url ? { url } : { value };
          const style = resolveSlackButtonStyle(button.style);
          return [
            {
              type: "button" as const,
              // Slack emits block_actions even for URL buttons; link-only actions must be ignored.
              action_id: url
                ? buildSlackReplyLinkActionId(state.buttonIndex + 1, choiceIndex)
                : buildSlackReplyButtonActionId(state.buttonIndex + 1, choiceIndex),
              text: {
                type: "plain_text" as const,
                text: truncateSlackText(button.label, SLACK_ACTION_LABEL_MAX),
                emoji: true,
              },
              ...target,
              ...(style ? { style } : {}),
            },
          ];
        })
        .slice(0, SLACK_ACTION_BLOCK_ELEMENTS_MAX);
      if (elements.length === 0) {
        return state;
      }
      state.blocks.push({
        type: "actions",
        block_id: `openclaw_reply_buttons_${++state.buttonIndex}`,
        elements,
      });
      return state;
    }
    const optionsLocal = block.options
      .map((option) => ({
        label: option.label,
        value: resolveSlackControlValue(option),
      }))
      .filter(isRenderableSlackOption)
      .slice(0, SLACK_STATIC_SELECT_OPTIONS_MAX);
    if (optionsLocal.length === 0) {
      return state;
    }
    state.blocks.push({
      type: "actions",
      block_id: `openclaw_reply_select_${++state.selectIndex}`,
      elements: [
        {
          type: "static_select",
          action_id: buildSlackReplySelectActionId(state.selectIndex),
          placeholder: {
            type: "plain_text",
            text: truncateSlackText(
              normalizeOptionalString(block.placeholder) ?? "Choose an option",
              SLACK_ACTION_LABEL_MAX,
            ),
            emoji: true,
          },
          options: optionsLocal.map((option, _choiceIndex) => ({
            text: {
              type: "plain_text",
              text: truncateSlackText(option.label, SLACK_ACTION_LABEL_MAX),
              emoji: true,
            },
            value: option.value,
          })),
        },
      ],
    });
    return state;
  }).blocks;
}

/** Render portable presentation blocks as Slack Block Kit blocks. */
export function buildSlackPresentationBlocks(
  presentation?: MessagePresentation,
  options: SlackInteractiveBlockRenderOptions = {},
): SlackBlock[] {
  if (!presentation) {
    return [];
  }
  const blocks: SlackBlock[] = [];
  if (presentation.title) {
    blocks.push({
      type: "header",
      text: {
        type: "plain_text",
        text: truncateSlackText(presentation.title, SLACK_HEADER_TEXT_MAX),
        emoji: true,
      },
    });
  }
  let buttonIndex = options.buttonIndexOffset ?? 0;
  let selectIndex = options.selectIndexOffset ?? 0;
  for (const block of presentation.blocks) {
    if (block.type === "text" || block.type === "context") {
      const text = block.text.trim();
      if (!text) {
        continue;
      }
      if (block.type === "context") {
        blocks.push({
          type: "context",
          elements: [{ type: "mrkdwn", text: truncateSlackText(text, SLACK_SECTION_TEXT_MAX) }],
        });
      } else {
        blocks.push({
          type: "section",
          text: { type: "mrkdwn", text: truncateSlackText(text, SLACK_SECTION_TEXT_MAX) },
        });
      }
      continue;
    }
    if (block.type === "divider") {
      blocks.push({ type: "divider" });
      continue;
    }
    if (block.type === "buttons") {
      const rendered = buildSlackPresentationButtonBlock(block, buttonIndex + 1);
      if (rendered) {
        buttonIndex += 1;
        blocks.push(rendered);
      }
      continue;
    }
    if (block.type === "select") {
      const rendered = buildSlackPresentationSelectBlock(block, selectIndex + 1);
      if (rendered) {
        selectIndex += 1;
        blocks.push(rendered);
      }
    }
  }
  return blocks;
}

function buildSlackPresentationButtonBlock(
  block: MessagePresentationButtonsBlock,
  buttonIndex: number,
): SlackBlock | undefined {
  const elements = block.buttons
    .flatMap((button, choiceIndex) => {
      const target = resolveSlackPresentationButtonTarget(button);
      if (!target) {
        return [];
      }
      const style = resolveSlackButtonStyle(button.style);
      return [
        {
          type: "button" as const,
          // Slack emits block_actions even for URL buttons; link-only actions must be ignored.
          action_id: target.url
            ? buildSlackReplyLinkActionId(buttonIndex, choiceIndex)
            : buildSlackReplyButtonActionId(buttonIndex, choiceIndex),
          text: {
            type: "plain_text" as const,
            text: truncateSlackText(button.label, SLACK_ACTION_LABEL_MAX),
            emoji: true,
          },
          ...target,
          ...(style ? { style } : {}),
        },
      ];
    })
    .slice(0, SLACK_ACTION_BLOCK_ELEMENTS_MAX);
  return elements.length > 0
    ? {
        type: "actions",
        block_id: `openclaw_reply_buttons_${buttonIndex}`,
        elements,
      }
    : undefined;
}

function resolveSlackPresentationButtonTarget(
  button: MessagePresentationButtonsBlock["buttons"][number],
): { value?: string; url?: string } | undefined {
  const callbackData = resolveSlackControlValue(button);
  const value =
    callbackData && isWithinSlackLimit(callbackData, SLACK_BUTTON_VALUE_MAX)
      ? callbackData
      : undefined;
  const rawUrl = button.url ?? button.webApp?.url ?? button.web_app?.url;
  const url = rawUrl && isWithinSlackLimit(rawUrl, SLACK_BUTTON_URL_MAX) ? rawUrl : undefined;
  return url ? { url } : value ? { value } : undefined;
}

/** True when native Slack rendering preserves every portable control. */
export function canRenderSlackPresentation(presentation: MessagePresentation): boolean {
  if (presentation.title && !isWithinSlackLimit(presentation.title.trim(), SLACK_HEADER_TEXT_MAX)) {
    return false;
  }
  return presentation.blocks.every((block) => {
    if (block.type === "text" || block.type === "context") {
      return isWithinSlackLimit(block.text.trim(), SLACK_SECTION_TEXT_MAX);
    }
    if (block.type === "buttons") {
      return (
        block.buttons.length <= SLACK_ACTION_BLOCK_ELEMENTS_MAX &&
        block.buttons.every((button) => resolveSlackPresentationButtonTarget(button) !== undefined)
      );
    }
    if (block.type === "select") {
      return (
        block.options.length <= SLACK_STATIC_SELECT_OPTIONS_MAX &&
        block.options.every((option) =>
          isRenderableSlackOption({
            label: option.label,
            value: resolveSlackControlValue(option),
          }),
        )
      );
    }
    return true;
  });
}

function buildSlackPresentationSelectBlock(
  block: MessagePresentationSelectBlock,
  selectIndex: number,
): SlackBlock | undefined {
  const options = block.options
    .map((option) => ({
      label: option.label,
      value: resolveSlackControlValue(option),
    }))
    .filter(isRenderableSlackOption)
    .slice(0, SLACK_STATIC_SELECT_OPTIONS_MAX);
  return options.length > 0
    ? {
        type: "actions",
        block_id: `openclaw_reply_select_${selectIndex}`,
        elements: [
          {
            type: "static_select",
            action_id: buildSlackReplySelectActionId(selectIndex),
            placeholder: {
              type: "plain_text",
              text: truncateSlackText(
                normalizeOptionalString(block.placeholder) ?? "Choose an option",
                SLACK_ACTION_LABEL_MAX,
              ),
              emoji: true,
            },
            options: options.map((option) => ({
              text: {
                type: "plain_text",
                text: truncateSlackText(option.label, SLACK_ACTION_LABEL_MAX),
                emoji: true,
              },
              value: option.value,
            })),
          },
        ],
      }
    : undefined;
}
