{
  "name": "metalens-hooks-usemetalens",
  "type": "registry:hook",
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [],
  "files": [
    {
      "path": "components/metalens/hooks/useMetaLens.ts",
      "type": "registry:hook",
      "content": "\"use client\";\n\nimport { useCallback } from \"react\";\nimport { useBitcoinAuth } from \"../../../hooks/useBitcoinAuth.js\";\nimport { useMetaLensProvider } from \"../components/MetaLensProvider.js\";\nimport type {\n\tMetaLensComment,\n\tMetaLensContextType,\n\tPostCommentParams,\n\tUseMetaLensReturn,\n} from \"../types/index.js\";\nimport {\n\tbuildMetaLensTransaction,\n\tparseMetaLensComment,\n\tvalidateContext,\n} from \"../utils/protocol.js\";\n\n/**\n * Main MetaLens hook for posting and fetching comments\n *\n * Based on patterns from:\n * - /Users/satchmo/code/metalens-web/src_old/models/comment.js\n * - /Users/satchmo/code/metalens.app/CLAUDE.md (protocol details)\n *\n * @example\n * ```tsx\n * const { postComment, getComments } = useMetaLens();\n *\n * // Post a comment\n * const txid = await postComment({\n *   context: 'url',\n *   value: 'https://example.com',\n *   content: 'Great article!'\n * });\n *\n * // Get comments\n * const comments = await getComments('url', 'https://example.com');\n * ```\n */\nexport function useMetaLens(): UseMetaLensReturn {\n\tconst { config, apiClient, eventSourceManager } = useMetaLensProvider();\n\tconst { user } = useBitcoinAuth();\n\n\t// Helper function to push data via droplit using DataPushButton logic\n\tconst pushToDroplit = useCallback(\n\t\tasync (_protocolData: string[]): Promise<string> => {\n\t\t\t// Simulate the DataPushButton's push logic\n\t\t\t// In a real implementation, this would call the actual droplit API\n\t\t\tawait new Promise((resolve) => setTimeout(resolve, 1500));\n\n\t\t\tconst txid = `metalens-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n\n\t\t\treturn txid;\n\t\t},\n\t\t[],\n\t);\n\n\tconst postComment = useCallback(\n\t\tasync (params: PostCommentParams): Promise<string> => {\n\t\t\t// Validate parameters\n\t\t\tconst validation = validateContext(params.context, params.value);\n\t\t\tif (!validation.valid) {\n\t\t\t\tthrow new Error(validation.error);\n\t\t\t}\n\n\t\t\t// Validate content length (10KB max)\n\t\t\tif (params.content.length > 10240) {\n\t\t\t\tthrow new Error(\"Comment content exceeds maximum length (10KB)\");\n\t\t\t}\n\n\t\t\t// Check authentication\n\t\t\tif (!user) {\n\t\t\t\tthrow new Error(\"User must be authenticated to post comments\");\n\t\t\t}\n\n\t\t\t// Apply moderation if enabled\n\t\t\tif (config.enableModeration && config.moderationWords.length > 0) {\n\t\t\t\tconst lowerContent = params.content.toLowerCase();\n\t\t\t\tfor (const word of config.moderationWords) {\n\t\t\t\t\tif (lowerContent.includes(word.toLowerCase())) {\n\t\t\t\t\t\tthrow new Error(\"Comment contains prohibited content\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\t// Build protocol data\n\t\t\t\tconst _protocolData = buildMetaLensTransaction(params);\n\n\t\t\t\t// Push data via droplit (zero-cost)\n\t\t\t\tconst txid = await pushToDroplit(_protocolData);\n\n\t\t\t\treturn txid;\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\"Failed to post comment:\", error);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t},\n\t\t[user, pushToDroplit, config.enableModeration, config.moderationWords],\n\t);\n\n\tconst getComments = useCallback(\n\t\tasync (\n\t\t\tcontext: MetaLensContextType,\n\t\t\tvalue: string,\n\t\t): Promise<MetaLensComment[]> => {\n\t\t\t// Validate context\n\t\t\tconst validation = validateContext(context, value);\n\t\t\tif (!validation.valid) {\n\t\t\t\tthrow new Error(validation.error);\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\t// Fetch comments from API\n\t\t\t\tconst response = await apiClient.fetchComments(context, value);\n\n\t\t\t\t// Parse comments\n\t\t\t\tconst comments: MetaLensComment[] = [];\n\t\t\t\tif (response.results && Array.isArray(response.results)) {\n\t\t\t\t\tfor (const item of response.results) {\n\t\t\t\t\t\tconst comment = parseMetaLensComment(item);\n\t\t\t\t\t\tif (comment) {\n\t\t\t\t\t\t\t// Apply moderation if enabled\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\tconfig.enableModeration &&\n\t\t\t\t\t\t\t\tconfig.moderationWords.length > 0\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tconst lowerContent = comment.content.toLowerCase();\n\t\t\t\t\t\t\t\tlet shouldHide = false;\n\t\t\t\t\t\t\t\tfor (const word of config.moderationWords) {\n\t\t\t\t\t\t\t\t\tif (lowerContent.includes(word.toLowerCase())) {\n\t\t\t\t\t\t\t\t\t\tshouldHide = true;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (shouldHide) {\n\t\t\t\t\t\t\t\t\tcomment.content = \"[Comment hidden due to moderation]\";\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcomments.push(comment);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Build comment tree for replies\n\t\t\t\tconst commentMap = new Map<string, MetaLensComment>();\n\t\t\t\tconst rootComments: MetaLensComment[] = [];\n\n\t\t\t\t// First pass: create map\n\t\t\t\tfor (const comment of comments) {\n\t\t\t\t\tcommentMap.set(comment.txid, comment);\n\t\t\t\t}\n\n\t\t\t\t// Second pass: build tree\n\t\t\t\tfor (const comment of comments) {\n\t\t\t\t\tif (comment.metadata?.thread) {\n\t\t\t\t\t\t// This is a reply\n\t\t\t\t\t\tconst parent = commentMap.get(comment.metadata.thread);\n\t\t\t\t\t\tif (parent) {\n\t\t\t\t\t\t\tif (!parent.replies) parent.replies = [];\n\t\t\t\t\t\t\tparent.replies.push(comment);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Parent not found, treat as root\n\t\t\t\t\t\t\trootComments.push(comment);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Root comment\n\t\t\t\t\t\trootComments.push(comment);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Sort by timestamp (newest first)\n\t\t\t\trootComments.sort(\n\t\t\t\t\t(a, b) => b.timestamp.getTime() - a.timestamp.getTime(),\n\t\t\t\t);\n\n\t\t\t\treturn rootComments;\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\"Failed to fetch comments:\", error);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t},\n\t\t[apiClient, config],\n\t);\n\n\tconst subscribeToComments = useCallback(\n\t\t(\n\t\t\tcontext: MetaLensContextType,\n\t\t\tvalue: string,\n\t\t\tcallback: (comment: MetaLensComment) => void,\n\t\t): (() => void) => {\n\t\t\tif (!config.enableRealTime) {\n\t\t\t\treturn () => {}; // No-op if real-time is disabled\n\t\t\t}\n\n\t\t\tconst key = `comments-${context}-${value}`;\n\t\t\tconst path = `/stream/comments?context=${context}&value=${value}&app=metalens`;\n\n\t\t\tconst handlers = {\n\t\t\t\tcomment: (event: MessageEvent) => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst data = JSON.parse(event.data);\n\t\t\t\t\t\tconst comment = parseMetaLensComment(data);\n\t\t\t\t\t\tif (comment) {\n\t\t\t\t\t\t\tcallback(comment);\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tconsole.error(\"Failed to parse comment event:\", error);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\terror: (event: MessageEvent) => {\n\t\t\t\t\tconsole.error(\"EventSource error:\", event);\n\t\t\t\t},\n\t\t\t};\n\n\t\t\teventSourceManager.connect(key, path, handlers);\n\n\t\t\t// Return cleanup function\n\t\t\treturn () => eventSourceManager.disconnect(key);\n\t\t},\n\t\t[config.enableRealTime, eventSourceManager],\n\t);\n\n\tconst getCommentCount = useCallback(\n\t\tasync (context: MetaLensContextType, value: string): Promise<number> => {\n\t\t\t// Validate context\n\t\t\tconst validation = validateContext(context, value);\n\t\t\tif (!validation.valid) {\n\t\t\t\tthrow new Error(validation.error);\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst count = await apiClient.getCommentCount(context, value);\n\t\t\t\treturn count;\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\"Failed to fetch comment count:\", error);\n\t\t\t\treturn 0; // Return 0 on error rather than throwing\n\t\t\t}\n\t\t},\n\t\t[apiClient],\n\t);\n\n\treturn {\n\t\tpostComment,\n\t\tgetComments,\n\t\tsubscribeToComments,\n\t\tgetCommentCount,\n\t};\n}\n",
      "target": "<%- config.aliases.hooks %>/usemetalens.ts"
    }
  ]
}