// Check if ~/.apptile-cli exists. If it doesn't create it and add workspaces table.

import DatabaseConstructor, {Database as SqliteDB} from 'better-sqlite3';
import os from 'os';
import path from 'path';
import {mkdirSync} from 'fs';
import {DataBase} from '../ui/src/types';
import {runMigrations} from './migrate';
import markdownit from 'markdown-it';
import Anthropic from '@anthropic-ai/sdk';
import { Message, MessageParam, ToolResultBlockParam, ToolUseBlockParam } from '@anthropic-ai/sdk/resources';
import { logger } from '../utils/logger';
import {Message as Bedrockmessage} from "@aws-sdk/client-bedrock-runtime";
import { ChatCompletionMessageParam, ChatCompletionMessageToolCall } from 'openai/resources/chat';
import chalk from 'chalk';
import { LLMProviders } from '../controllers/agents/baseAgent';
import Chat, { IChatAttributes } from '../models/chat';
import ChatMessage from '../models/chatmessage';
const md = markdownit();

const isPostgresEnabled = process.env.ENABLE_POSTGRES == 'true';

const homeDir = os.homedir();
try {
  mkdirSync(path.resolve(homeDir, '.apptile-cli'));
} catch (err: any) {
  if (err?.code === 'EEXIST') {
    console.log("config folder already exists");
  } else {
    throw err;
  }
}
const dbFilePath = path.resolve(homeDir, '.apptile-cli/db.sqlite');
export const db: SqliteDB = new DatabaseConstructor(dbFilePath); // , { verbose: console.log });

runMigrations(db);

export type AgentProgramType = 'widget'|'datasource'|'app';

function narrowChat(raw: any): raw is IChatAttributes {
  return (raw &&
          (typeof raw.id === 'number') &&
          (typeof raw.outputFile === 'string') &&
          (['widget', 'datasource', 'app'].includes(raw.type)) &&
          (typeof raw.createdAt === 'string' || typeof raw.createdAt === 'object') &&
          (["openai", "claude", "google", "amazon"].includes(raw.llm_provider)) &&
          (typeof raw.llm_model === "string")
        );
}

function narrowChatArray(raw: any): raw is IChatAttributes[] {
  try {
    let result = true;
    for (let i = 0; i < raw.length; ++i) {
      result = result && narrowChat(raw[i]);
    }
    return result;
  } catch (err) {
    console.error("Failed to validate chat array", err);
    return false;
  }
}

const selectChat = db.prepare(`SELECT * FROM chat WHERE id = ?`);
const insertChat = db.prepare(`INSERT INTO chat (outputFile, type, llm_provider, llm_model, appId) VALUES (?,?,?,?,?)`);
export async function createChat(filePath: string, type: AgentProgramType, provider: LLMProviders, model: string, appId: string) {
  let chat;
  if (isPostgresEnabled) {
    const insertChatData = await Chat.create({
      outputFile: filePath,
      type,
      llm_provider: provider,
      llm_model: model,
      appId
    });
    chat = insertChatData.get({ plain: true });
  } else {
    const { lastInsertRowid } = insertChat.run(filePath, type, provider, model, appId);
    chat = selectChat.get(lastInsertRowid);
    console.log("Creating new chat: ", chat);
  }


  if (narrowChat(chat)) {
    return chat;
  } else {
    return null;
  }
}


const selectChatForArtefact = db.prepare(`SELECT * FROM chat WHERE outputFile = ?`);
export async function getChat(filePath: string): Promise<IChatAttributes | null> {
  let chat;
  if (isPostgresEnabled) {
    chat = await Chat.findOne({
      where: {
        outputFile: filePath
      }
    });
  } else{
    chat = selectChatForArtefact.get(filePath);
  }

  if (chat && narrowChat(chat)) {
    return chat;
  } else {
    return null;
  }
}

export interface ApptileInMemoryChatMessage {
  role: 'user'|'assistant';
  content_type: 'text'|'tool_use'|'tool_result'|'text/plain'|'image/jpeg'|'image/png';
  content: string;
  tool_call_id: string | null;
  tool_name: string | null;
  temp_id: string | null;
}

export interface ApptileChatMessage extends ApptileInMemoryChatMessage {
  id: number;
  chatId: number;
  createdAt: Date;
  htmlcontent: string | null;
};

export function toInMemoryChatMessage(messages: ApptileChatMessage[]): ApptileInMemoryChatMessage[] {
  return messages.map(it => {
    return {
      role: it.role,
      content_type: it.content_type,
      content: it.content,
      tool_call_id: it.tool_call_id,
      tool_name: it.tool_name,
      temp_id: it.temp_id
    };
  })
}

function isImageMedia(mimetype: string): mimetype is "image/png"|"image/jpeg" {
  return ["image/png", "image/jpeg"].includes(mimetype);
}

export function toAnthropicMessages(messages: ApptileInMemoryChatMessage[]): MessageParam[] {
  let result: MessageParam[] = [];
  for (let i = 0; i < messages.length; ++i) {
    let message = messages[i];
    let transformedMessage: MessageParam|null = null;
    if (message.role === "assistant") { 
      if (message.content_type === "text") {
        transformedMessage = {
          role: message.role,
          content: [
            {
              type: "text",
              text: message.content
            }
          ]
        };
      } else if (
        message.content_type === "tool_use" && 
        message.tool_name && 
        message.content && 
        message.tool_call_id
      ) {
        transformedMessage = {
          role: message.role,
          content: [
            {
              type: "tool_use",
              id: message.tool_call_id,
              name: message.tool_name,
              input: JSON.parse(message.content)
            }
          ]
        }
      } else {
        logger.error("Cannot process this assistant message: ");
      }
    } else if (message.role === "user") {
      if (message.content_type === "text") {
        transformedMessage = {
          role: message.role,
          content: [
            {
              type: "text",
              text: message.content
            }
          ]
        };
      } else if (message.content_type === "tool_result" && message.tool_call_id) {
        transformedMessage = {
          role: message.role,
          content: [
            {
              type: "tool_result",
              tool_use_id: message.tool_call_id,
              content: message.content
            }
          ]
        };
      } else if (isImageMedia(message.content_type)) {
        transformedMessage = {
          role: message.role,
          content: [
            {
              type: "image",
              source: {
                type: "base64",
                media_type: message.content_type,
                data: message.content
              }
            }
          ]
        }
      } else {
        logger.error("Cannot process this tool user message: ");
      }
    } else {
      throw new Error("Unreachable codepath!");
    }

    if (transformedMessage) {
      result.push(transformedMessage);
    }
  }

  return result;
}

function base64ToUint8Array(base64: string): Uint8Array {
  const binaryStr = atob(base64);
  const len = binaryStr.length;
  const bytes = new Uint8Array(len);
  for (let i = 0; i < len; i++) {
    bytes[i] = binaryStr.charCodeAt(i);
  }
  return bytes;
}


export function toAmazonMessages(messages: ApptileInMemoryChatMessage[]): Bedrockmessage[] {
  let result: Bedrockmessage[] = [];
  for (let i = 0; i < messages.length; ++i) {
    let message = messages[i];
    let transformedMessage: Bedrockmessage|null = null;
    if (message.role === "assistant") { 
      if (message.content_type === "text") {
        transformedMessage = {
          role: message.role,
          content: [
            {
              text: message.content
            }
          ]
        };
      } else if (
        message.content_type === "tool_use" && 
        message.tool_name && 
        message.content && 
        message.tool_call_id
      ) {
        transformedMessage = {
          role: message.role,
          content: [
            {
              toolUse: {
                toolUseId : message.tool_call_id,
                name: message.tool_name,
                input: JSON.parse(message.content)
              }
            }
          ]
        }
      } else {
        logger.error("Cannot process this assistant message: ");
      }
    } else if (message.role === "user") {
      if (message.content_type === "text") {
        transformedMessage = {
          role: message.role,
          content: [
            {
              text: message.content
            }
          ]
        };
      } else if (message.content_type === "tool_result" && message.tool_call_id) {
        transformedMessage = {
          role: message.role,
          content: [
            {
              toolResult: {
                toolUseId: message.tool_call_id,
                content: [
                  {
                    text: message.content
                  }
                ]
              }
              
            }
          ]
        };
      } else if (isImageMedia(message.content_type)) {
        transformedMessage = {
          role: message.role,
          content: [
            {
              image: {
                format: message.content_type === "image/jpeg" ? "jpeg" : "png",
                source: {
                  bytes: base64ToUint8Array(message.content),
                }
              }
            }
          ]
        }
      } else {
        logger.error("Cannot process this tool user message: ");
      }
    } else {
      throw new Error("Unreachable codepath!");
    }

    if (transformedMessage) {
      result.push(transformedMessage);
    }
  }

  return result;
}

export function toOpenaiMessages(messages: ApptileInMemoryChatMessage[], systemPrompt: string): ChatCompletionMessageParam[] {
  let result: ChatCompletionMessageParam[] = [];
  result.push({
    role: 'system',
    content: systemPrompt
  })

  for (let i = 0; i < messages.length; ++i) {
    let message = messages[i];
    let transformedMessage: ChatCompletionMessageParam|null = null;
    if (message.role === "assistant") { 
      if (message.content_type === "text") {
        transformedMessage = {
          role: message.role,
          content: [
            {
              type: "text",
              text: message.content
            }
          ]
        };
      } else if (
        message.content_type === "tool_use" && 
        message.tool_name && 
        message.content && 
        message.tool_call_id
      ) {
        transformedMessage = {
          role: "assistant",
          tool_calls: [{
            id: message.tool_call_id,
            type: "function",
            function: {
              name: message.tool_name,
              arguments: message.content
            }
          }]
        }
      } else {
        logger.error("Cannot process this assistant message: " + JSON.stringify(message));
      }
    } else if (message.role === "user") {
      if (message.content_type === "text") {
        transformedMessage = {
          role: message.role,
          content: [
            {
              type: "text",
              text: message.content
            }
          ]
        };
      } else if (message.content_type === "tool_result" && message.tool_call_id) {
        transformedMessage = {
          role: "tool",
          tool_call_id: message.tool_call_id,
          content: [
            {
              type: "text",
              text: message.content
            }
          ]
        };
      } else {
        logger.error("Cannot process this tool user message: " + JSON.stringify(message));
      }
    } else {
      throw new Error("Unreachable codepath!");
    }

    if (transformedMessage) {
      result.push(transformedMessage);
    }
  }
  
  return result;
}

function narrowChatMessage(raw: any): raw is ApptileChatMessage {
  try {
    return (raw &&
          (typeof raw.id === 'number') &&
          (typeof raw.chatId === 'number') &&
          (['user', 'assistant'].includes(raw.role)) &&
          (['text', 'tool_use', 'tool_result', 'text/plain', 'image/jpeg', 'image/png'].includes(raw.content_type)) &&
          (typeof raw.content === 'string') &&
          (!raw.tool_call_id || typeof raw.tool_call_id === 'string') &&
          (typeof raw.createdAt === 'string' || typeof raw.createdAt === 'object') &&
          (raw.htmlcontent === null || typeof raw.htmlcontent === 'string') &&
          (!raw.tool_name || typeof raw.tool_name === 'string')
         );
  } catch(err) {
    console.error("Failed to validate chat message: ", raw);
    return false;
  }
}

function narrowChatMessageArray(raw: any): raw is Array<ApptileChatMessage> {
  let result = false;
  try {
    result = Array.isArray(raw);
    for (let i = 0; i < raw.length; ++i) {
      result = result && narrowChatMessage(raw[i]);
    }
  } catch (err) {
    console.error("Failed to validate chat message array", err);
  }
  return result;
}

const selectMessagesByChatDesc = db.prepare(`SELECT * FROM chat_message WHERE chatId = ? ORDER BY id DESC`);
const selectMessagesByChatAsc = db.prepare(`SELECT * FROM chat_message WHERE chatId = ? ORDER BY id ASC`);

const selectMessagesByChatDescWithLimit = db.prepare(`SELECT * FROM (SELECT * FROM chat_message WHERE chatId = ? ORDER BY id DESC LIMIT ?) ORDER BY id ASC`);
const selectMessagesByChatAscWithLimit = db.prepare(`SELECT * FROM chat_message WHERE chatId = ? ORDER BY id ASC LIMIT ?`);
export async function getChatMessages(chatId: number, dateOrder: 'ASC'|'DESC', limit?: number): Promise<ApptileChatMessage[]> {
  let messages;

  if (isPostgresEnabled) {
    const options: any = {
      where: { chatId },
      order: [["id", dateOrder]],
      raw: true
    };

    if (limit) {
      options.limit = limit;
      options.subQuery = false
    }
    
    messages = await ChatMessage.findAll(options);

    if (dateOrder == 'DESC' && limit) {
      messages.reverse();
    }

  } else {
    if (limit) {
      if (dateOrder === 'DESC') {
        messages = selectMessagesByChatDescWithLimit.all(chatId, limit);
      } else {
        messages = selectMessagesByChatAscWithLimit.all(chatId, limit);
      }
    } else {
      if (dateOrder === 'DESC') {
        messages = selectMessagesByChatDesc.all(chatId);
      } else {
        messages = selectMessagesByChatAsc.all(chatId);
      }
    }
  }

  if (narrowChatMessageArray(messages)) {
    return messages;
  } else {
    return [];
  }
}

export function sequenceToolCallsAndAddEmptyResults<T extends ApptileInMemoryChatMessage>(messages: T[]): T[] {
  let result: T[] = [];
  const toolCalls: Record<string, {tool_resp: T; tool_call: T}> = {};
  
  // First pass: build mapping of tool calls and responses
  for (let i = 0; i < messages.length; ++i) {
    const current = messages[i];
    if (current.content_type === "tool_use" && current.tool_call_id) {
      toolCalls[current.tool_call_id] = {
        ...toolCalls[current.tool_call_id], 
        tool_call: current 
      };
    } else if (current.content_type === "tool_result" && current.tool_call_id) {
      toolCalls[current.tool_call_id] = {
        ...toolCalls[current.tool_call_id],
        tool_resp: current
      };
    }
  }

  // Second pass: sequence messages and add empty tool results where needed
  for (let i = 0; i < messages.length; ++i) {
    const current = messages[i];
    if (current.content_type === "tool_use") {
      if (current.tool_call_id) {
        result.push(current);
        if (toolCalls[current.tool_call_id].tool_resp) {
          result.push(toolCalls[current.tool_call_id].tool_resp);
        } else {
          // Add empty tool result if none exists
          const emptyResult = {
            ...current,
            role: "user",
            content_type: "tool_result",
            content: " "
          } as T;
          result.push(emptyResult);
          logger.debug("Added empty tool_result for tool_call: " + JSON.stringify(current));
        }
      } else {
        logger.error("Dropping tool_call that doesn't have an id: " + JSON.stringify(current));
      }
    } else if (current.content_type !== "tool_result") {
      result.push(current);
    }
  }

  return result;
}

export function sequenceToolCallAndResponses<T extends ApptileInMemoryChatMessage>(messages: T[], includeIncompleteToolUse: boolean = false): T[] {
  let result: T[] = [];
  const toolCalls: Record<string, {tool_resp: T; tool_call: T}> = {};
  for (let i = 0; i < messages.length; ++i) {
    const current = messages[i];
    if (current.content_type === "tool_use" && current.tool_call_id) {
      toolCalls[current.tool_call_id] = {
        ...toolCalls[current.tool_call_id], 
        tool_call: current 
      };
    } else if (current.content_type === "tool_result" && current.tool_call_id) {
      toolCalls[current.tool_call_id] = {
        ...toolCalls[current.tool_call_id],
        tool_resp: current
      };
    }
  }

  for (let i = 0; i < messages.length; ++i) {
    const current = messages[i];
    if (current.content_type === "tool_use") {
      if (current.tool_call_id) {
        if (includeIncompleteToolUse) {
          result.push(current);
          if (toolCalls[current.tool_call_id].tool_resp) {
            result.push(toolCalls[current.tool_call_id].tool_resp);
          }
        } else if (toolCalls[current.tool_call_id].tool_resp) {
          result.push(current);
          result.push(toolCalls[current.tool_call_id].tool_resp);
        } else {
          // logger.error("Dropping tool_call because tool_response does not exist: " + JSON.stringify(current));
        }
      } else {
        logger.error("Dropping tool_call that doesn't have an id: " + JSON.stringify(current));
      }
    } else if (current.content_type !== "tool_result") {
      result.push(current);
    }
  }

  return result;
}

const insertChatWithContentAndRole = db.prepare(`INSERT INTO chat_message (chatId, tool_call_id, tool_name, content_type, role, content, htmlcontent, temp_id) VALUES (?,?,?,?,?,?,?,?)`);
export async function persistChatMessage(
  chatId: number, 
  tool_call_id: string | null,
  tool_name: string | null,
  content_type: 'text'|'tool_use'|'tool_result'|'text/plain'|'image/jpeg'|'image/png'|'image/gif',
  role: 'user'|'assistant', 
  content: string,
  temp_id: string
) {
  let htmlContent = "";
  if (role === "assistant") {
    try {
      const parsedmessage = JSON.parse(content)?.[0];
      if (parsedmessage.type === "tool_use") {
        if (parsedmessage.name === "nocodelayer_generate_code_for_plugin") {
          htmlContent = md.renderInline('Prompt for plugin agent to generate code for *`' + parsedmessage.input.name + '`*\n'); 
          htmlContent += md.renderInline(parsedmessage.input.prompt);
        } else {
          htmlContent = md.render(
            '*Calling tool* `' + parsedmessage.name + '`\n' +
            '```\n' + JSON.stringify(parsedmessage.input, null, 2) + '\n```'
          );
        }
      } else if (parsedmessage.type === "text") {
        htmlContent = md.render(parsedmessage.text);
      }
    } catch (err) {
      try {
        htmlContent = md.render(content);
      } catch (err) {
        htmlContent = "";
      }
    }
  } else if (role === "user") { 
    let processedContent = content;
    if (content.includes("image_included_in_next_message")) {
      processedContent = content.replace(/(?:&nbsp;)?`image_included_in_next_message`/g, '');
    }
    htmlContent = md.render(processedContent);
  } 

  if (isPostgresEnabled) {
    await ChatMessage.create({ 
      chatId,
      tool_call_id: tool_call_id as string,
      tool_name: tool_name as string,
      content_type: content_type,
      role: role,
      content: content,
      htmlcontent: htmlContent,
      temp_id: temp_id
    });
  } else {
    insertChatWithContentAndRole.run(
      chatId, tool_call_id, tool_name, content_type, role, content, htmlContent, temp_id
    );
  }
}
const insertToolCall = db.prepare(`INSERT INTO chat_message (chatId, role, content, tool_call_id) VALUES (?,?,?,?)`);
export async function createToolChatMessage(chatId: number, role: 'tool', content: string, tool_call_id: string) {
  if (isPostgresEnabled) {
    await ChatMessage.create({ 
      chatId,
      role: role,
      content: content,
      tool_call_id: tool_call_id
    });
  } else {
    insertToolCall.run(chatId, role, content, tool_call_id);
  }
}

type APIKey = {
  id: number;
  type: LLMProviders|"pexels";
  apikey: string;
};

function narrowAPIKey(raw: any): raw is APIKey {
  try {
    if (raw && 
        (typeof raw.id === 'number') &&
        (["openai", "claude", "pexels", "google", "aws_access_key_id", "aws_secret_access_key"].includes(raw.type)) &&
        (typeof raw.apikey === 'string')
    ) {
      return true;
    }
  } catch (err) {
    console.error("Failed to validate APIKey", err);
  }
  return false;
}

const selectAPIKey = db.prepare(`SELECT * FROM apikey where type=?`);
export function getAPIKey(type: LLMProviders|"pexels"|"aws_access_key_id"|"aws_secret_access_key"): APIKey|null {
  let key = selectAPIKey.get(type);
  if (narrowAPIKey(key)) {
    return key;
  } else {
    return null;
  }
}

export type MetroProc = {
  pid: number;
  repoPath: string;
};

function narrowMetroProc(raw: any): raw is MetroProc {
  try {
    return (typeof raw.pid === "number") &&
      (typeof raw.repoPath === "string");
  } catch (err) {
    console.error("Failed to narrow metro proc");
    return false;
  }
}

function narrowMetroProcArray(raw: any): raw is MetroProc[] {
  try {
    let result = true;
    for (let i = 0; i < raw.length; ++i) {
      result = result && narrowMetroProc(raw[i]);
    }
    return result;
  } catch (err) {
    console.error("Failed to validate metroProc array");
    return false;
  }
}

const delMetroProc = db.prepare(`DELETE FROM metro WHERE repoPath=?`);
export function deleteMetroProc(repoPath: string) {
  delMetroProc.run(repoPath);
}

const selectMetroProc = db.prepare(`SELECT * FROM metro`);
export function getCurrentMetroProc(): null|MetroProc[] {
  let procs = selectMetroProc.all();
  if (narrowMetroProcArray(procs)) {
    return procs;
  } else {
    return null;
  }
}

const insertMetroProc = db.prepare(`INSERT INTO metro (pid, repoPath) VALUES (?,?)`);
export function createMetroProc(pid: number, fullPath: string) {
  insertMetroProc.run(pid.toFixed(0), fullPath);
}

const insertWorkspace = db.prepare(`INSERT INTO workspaces (location)
               VALUES (?)`);
export function createWorkspace(location: string ): void {
  try {
    insertWorkspace.run(location);
  } catch (err) {
    console.error("Failed to create link", err);
  }
}

const removeWorkspace = db.prepare(`DELETE FROM workspaces WHERE id = ?`);
export function deleteLink(id: number) {
  removeWorkspace.run(id);
}

const getAllWorkspaces = db.prepare(`SELECT * FROM workspaces`);
export function getWorkspaces(): DataBase.Workspace[]|null {
  const workspaces = getAllWorkspaces.all();
  
  if (DataBase.narrowWorkspaceArray(workspaces)) {
    return workspaces;
  } else {
    console.error("Invalid link found in table", workspaces);
    return null;
  }
}

const findWorkspace = db.prepare(`SELECT * FROM workspaces WHERE id = ?`);
export function getWorkspace(id: number): DataBase.Workspace|null {
  const workspace = findWorkspace.get(id.toFixed(0));
  if (DataBase.narrowWorkspace(workspace)) {
    return workspace;
  } else {
    console.error("Invalid workspace found in table", workspace);
    return null;
  }
}

const findWorkspaceByLocation = db.prepare(`SELECT * FROM workspaces WHERE location = ?`);
export function getWorkspaceByLocation(location: string): DataBase.Workspace|null {
  const workspace = findWorkspaceByLocation.get(location);
  if (DataBase.narrowWorkspace(workspace)) {
    return workspace;
  } else {
    console.error("Invalid workspace found in table", workspace);
    return null;
  }
}

function narrowOpenedApp(result: any): result is DataBase.OpenedApp {
  return (
    (result) &&
    (typeof (result.appId) === "string") &&
    (typeof (result.workspaceId === "string")) &&
    (typeof (result.repoPath) === "string") && 
    (!!result.repoPath) &&
    ((result.apptilecookie === null) || (typeof result.apptilecookie === "string"))
  );
}

const insertOrUpdateOpenapps = db.prepare(`INSERT INTO openapps (workspaceId, appId, repoPath) 
  VALUES (?, ?, ?)
  ON CONFLICT(appId)
  DO UPDATE SET 
    workspaceId = excluded.workspaceId,
    repoPath = excluded.repoPath
  `);
export function insertOrUpdateOpenapp(workspaceId: string, appId: string, repoPath: string) {
  insertOrUpdateOpenapps.run(workspaceId, appId, repoPath);
}

const findOpenApp = db.prepare(`SELECT * FROM openapps WHERE appId = ?`);
export async function getOpenApp(appId: string): Promise<DataBase.OpenedApp|null>{
  const openedAppEntry = findOpenApp.get(appId);
  if (narrowOpenedApp(openedAppEntry)) {
    return openedAppEntry;
  } else {
    return null;
  }
}

export function closeAppByLocation(repoPath: string) {
  // No op function
  return () => {};
}

const selectChatsByAppId = db.prepare(`SELECT * FROM chat WHERE appId = ?`);
export async function getChatsByAppId(appId: string): Promise<IChatAttributes[]> {
  let chats;
  if (isPostgresEnabled) {
    chats = await Chat.findAll({ where: { appId } });
  }

  chats = selectChatsByAppId.all(appId);
  
  if (narrowChatArray(chats)) {
    return chats;
  } else {
    return [];
  }
}

function narrowImages(imgs: any[]): imgs is Array<{content: string; content_type: string;}> {
  let result = true;
  for (let i = 0; i < imgs.length; ++i) {
    result = result && (typeof imgs[i].content === "string");
    result = result && (typeof imgs[i].content_type === "string");
  }

  return result;
}
const imagesFromPlanner = db.prepare(`SELECT content, content_type FROM chat_message 
WHERE chatId IN (
	SELECT id FROM chat WHERE outputFile = ?
) AND content_type LIKE 'image/%'`);
export function getImagesFromPlanner(appId: string): Array<{content: string;}> {
  const images = imagesFromPlanner.all(appId);
  if (narrowImages(images)) {
    return images;
  } else {
    return [];
  }
}
export default db;
