export interface AdditionalMount {
  hostPath: string; // Absolute path on host (supports ~ for home)
  containerPath?: string; // Optional — defaults to basename of hostPath. Mounted at /workspace/extra/{value}
  readonly?: boolean; // Default: true for safety
}

/**
 * Mount Allowlist - Security configuration for additional mounts
 * This file should be stored at ~/.config/nanoclaw/mount-allowlist.json
 * and is NOT mounted into any container, making it tamper-proof from agents.
 */
export interface MountAllowlist {
  // Directories that can be mounted into containers
  allowedRoots: AllowedRoot[];
  // Glob patterns for paths that should never be mounted (e.g., ".ssh", ".gnupg")
  blockedPatterns: string[];
  // If true, non-main groups can only mount read-only regardless of config
  nonMainReadOnly: boolean;
}

export interface AllowedRoot {
  // Absolute path or ~ for home (e.g., "~/projects", "/var/repos")
  path: string;
  // Whether read-write mounts are allowed under this root
  allowReadWrite: boolean;
  // Optional description for documentation
  description?: string;
}

export interface ContainerConfig {
  additionalMounts?: AdditionalMount[];
  timeout?: number; // Default: 300000 (5 minutes)
}

export interface RegisteredGroup {
  name: string;
  folder: string;
  added_at: string;
  containerConfig?: ContainerConfig;
  requiresTrigger?: boolean; // Default: true for groups, false for solo chats
  isMain?: boolean; // True for the main control group (no trigger, elevated privileges)
  tenantId?: string; // Tenant that owns this group (multi-tenant mode)
  hooksToken?: string | null; // Bearer token for hooks endpoints
  hooksEnabled?: boolean; // Whether hooks are enabled for this group
}

export interface Tenant {
  id: string;
  name: string;
  email?: string;
  provider?: 'claude' | 'codex' | 'pi';
  pi_backend?: string; // LLM backend for pi: 'anthropic' (default) | 'openai' | 'gemini'
  tenant_type?: 'standard' | 'bot';
  dashboard_password?: string;
  discord_category_id?: string;
  discord_role_id?: string;
  discord_invite?: string;
  discord_bot_token?: string;
  discord_user_id?: string;
  discord_pairing_code?: string | null;
  anthropic_key?: string;
  max_concurrent: number;
  max_memory_mb: number;
  cpu_quota: string;
  cpu_weight: number;
  max_disk_mb: number;
  port_range_start?: number;
  port_range_size?: number;
  host_mount?: string; // Mount host path into container at /host (for admin/privileged tenants)
  android_sdk?: boolean; // Mount shared Android SDK read-only at /opt/android-sdk
  proxy_enabled?: boolean; // Tenant has tailscale SOCKS5 proxy access (auto-writes proxy.json)
  is_admin?: boolean; // Admin tenant: gets SSH access and admin skill
  show_progress_messages?: boolean | number | string; // User-facing tool/progress messages in chat
  credential_id?: string; // Pin to a specific credential from the pool
  model?: string; // Provider-specific model override
  status: 'active' | 'suspended' | 'archived';
  created_at: string;
}

export interface Credential {
  id: string;
  provider?: 'claude' | 'codex' | 'pi';
  pi_backend?: string; // for provider=pi: which backend provider this credential covers
  type: 'oauth' | 'apikey';
  label?: string;
  status: 'active' | 'paused' | 'expired' | 'error';
  // OAuth fields
  access_token?: string;
  refresh_token?: string;
  expires_at?: number; // epoch ms
  // API key fields
  api_key?: string;
  auth_json?: string;
  // Metadata
  plan_type?: string;
  rate_limit_tier?: string;
  last_used_at?: string;
  total_requests: number;
  error_message?: string;
  created_at: string;
}

export interface Attachment {
  kind: 'image' | 'document' | 'text' | 'audio' | 'file';
  data?: string; // base64 for image/document, raw text for text files
  mediaType: string;
  name: string;
  sourcePath?: string; // Host-side path for file-backed attachments before container launch
  mountedPath?: string; // Container-visible path for mounted attachments
}
/** @deprecated use Attachment */
export type ImageAttachment = Attachment;

export interface SessionRecord {
  id: string;
  runtime: 'claude' | 'codex' | 'pi';
}

export interface NewMessage {
  id: string;
  chat_jid: string;
  sender: string;
  sender_name: string;
  content: string;
  timestamp: string;
  is_from_me?: boolean;
  is_bot_message?: boolean;
  is_trigger?: boolean;
  attachments?: Attachment[];
}

export interface ScheduledTask {
  id: string;
  tenant_id?: string;
  group_folder: string;
  chat_jid: string;
  prompt: string;
  schedule_type: 'cron' | 'interval' | 'once';
  schedule_value: string;
  context_mode: 'group' | 'isolated';
  next_run: string | null;
  last_run: string | null;
  last_result: string | null;
  status: 'active' | 'paused' | 'completed';
  created_at: string;
}

export interface TaskRunLog {
  task_id: string;
  run_at: string;
  duration_ms: number;
  status: 'success' | 'error';
  result: string | null;
  error: string | null;
}

// --- Channel abstraction ---

export interface Channel {
  name: string;
  connect(): Promise<void>;
  sendMessage(jid: string, text: string): Promise<void>;
  isConnected(): boolean;
  ownsJid(jid: string): boolean;
  disconnect(): Promise<void>;
  // Optional: typing indicator. Channels that support it implement it.
  setTyping?(jid: string, isTyping: boolean): Promise<void>;
  // Optional: send a file attachment.
  sendFile?(jid: string, filePath: string, caption?: string): Promise<void>;
  // Optional: sync group/chat names from the platform.
  syncGroups?(force: boolean): Promise<void>;
  // Optional: returns true if this JID is a voice text channel (TTS-enabled).
  isVoiceTextChannel?(jid: string): boolean;
}

// Callback type that channels use to deliver inbound messages
export type OnInboundMessage = (chatJid: string, message: NewMessage) => void;

// Callback for chat metadata discovery.
// name is optional — channels that deliver names inline (Telegram) pass it here;
// channels that sync names separately (via syncGroups) omit it.
export type OnChatMetadata = (
  chatJid: string,
  timestamp: string,
  name?: string,
  channel?: string,
  isGroup?: boolean,
) => void;
