"use client"; import { useState, useMemo, useEffect } from "react"; import { Loader2, Check, X, AlertCircle, ExternalLink, Plus, Trash2 } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Textarea } from "@/components/ui/textarea"; import { Badge } from "@/components/ui/badge"; import { cn } from "@/lib/utils"; import { channelsApi } from "@/lib/api"; import type { Channel } from "@/lib/types/api"; export type ParsedChannel = { input: string; type: "invite_link" | "username" | "telemetr" | "tgstat" | "unknown"; normalized: string | null; isValid: boolean; channel?: Channel; error?: string; }; interface ChannelInputProps { value: ParsedChannel[]; onChange: (channels: ParsedChannel[]) => void; placeholder?: string; maxItems?: number; } const CHANNEL_PATTERNS = [ { regex: /^https:\/\/t\.me\/(.+)$/i, type: "username", normalize: (m: string) => m.replace(/@/, "").toLowerCase() }, { regex: /^https:\/\/t\.me\/+([a-zA-Z0-9_]+)$/i, type: "invite_link", normalize: (m: string) => m.toUpperCase() }, { regex: /^@([a-zA-Z0-9_]+)$/i, type: "username", normalize: (m: string) => m.replace(/@/, "").toLowerCase() }, { regex: /^([a-zA-Z0-9_]+)$/i, type: "username", normalize: (m: string) => m.toLowerCase() }, { regex: /^https:\/\/telemetr\.me\/content\/([a-zA-Z0-9_]+)$/i, type: "telemetr", normalize: (m: string) => m.toLowerCase() }, { regex: /^https:\/\/tgstat\.ru\/channel\/@?([a-zA-Z0-9_]+)$/i, type: "tgstat", normalize: (m: string) => m.replace(/@/, "").toLowerCase() }, ]; export function ChannelInput({ value, onChange, placeholder = "Введите каналы (ссылки или @username), каждый с новой строки", maxItems = 50, }: ChannelInputProps) { const [input, setInput] = useState(""); const [parsing, setParsing] = useState(false); const [validating, setValidating] = useState(false); const parsedResults = useMemo(() => { return value; }, [value]); const handleParse = async () => { if (!input.trim()) return; setParsing(true); const lines = input .split(/[\n,;]/) .map((s) => s.trim()) .filter(Boolean); const newChannels: ParsedChannel[] = []; for (const line of lines) { let matched = false; for (const pattern of CHANNEL_PATTERNS) { const match = line.match(pattern.regex); if (match) { const normalized = pattern.normalize(match[1]); newChannels.push({ input: line, type: pattern.type as ParsedChannel["type"], normalized, isValid: false, }); matched = true; break; } } if (!matched) { newChannels.push({ input: line, type: "unknown", normalized: null, isValid: false, error: "Неизвестный формат", }); } } setParsing(false); // Validate through API await validateChannels(newChannels); }; const validateChannels = async (channels: ParsedChannel[]) => { setValidating(true); const results: ParsedChannel[] = []; for (const channel of channels) { if (!channel.normalized) { results.push(channel); continue; } try { // Try to find channel by different identifiers let foundChannel: Channel | undefined; if (channel.type === "invite_link") { // Search by invite link pattern const allChannels = await channelsApi.list({ size: 100 }); foundChannel = allChannels.items.find( (c) => c.username?.toUpperCase() === channel.normalized?.toUpperCase() ); } else { // Search by username const allChannels = await channelsApi.list({ size: 100 }); foundChannel = allChannels.items.find( (c) => c.username?.toLowerCase() === channel.normalized?.toLowerCase() ); } if (foundChannel) { results.push({ ...channel, isValid: true, channel: foundChannel, }); } else { results.push({ ...channel, isValid: false, error: "Канал не найден в базе", }); } } catch (err) { results.push({ ...channel, isValid: false, error: "Ошибка проверки", }); } } setValidating(false); onChange([...parsedResults, ...results].slice(0, maxItems)); setInput(""); }; const handleRemove = (index: number) => { const newValue = [...parsedResults]; newValue.splice(index, 1); onChange(newValue); }; const handleClear = () => { onChange([]); setInput(""); }; const getTypeIcon = (type: ParsedChannel["type"]) => { switch (type) { case "invite_link": return "🔗"; case "username": return "@"; case "telemetr": return "📊"; case "tgstat": return "📈"; default: return "?"; } }; const validCount = parsedResults.filter((c) => c.isValid).length; const invalidCount = parsedResults.filter((c) => !c.isValid).length; const existingCount = parsedResults.filter((c) => c.isValid && c.channel).length; const newCount = parsedResults.filter((c) => c.isValid && !c.channel).length; return (