// MyYarnsSDK.swift — iOS SDK for MyYarns live chat
// Full docs: https://myyarns.sendbaba.com/developers
// Requires iOS 15+, Swift 5.7+
// SPM: https://github.com/Sendbabahq/myyarns-ios
//
// SwiftUI usage:
//   ZStack { ContentView(); MyYarnsLauncherView(widgetKey: "sb_chat_xxxx") }.ignoresSafeArea()
//
// UIKit usage:
//   MyYarns.shared.configure(widgetKey: "sb_chat_xxxx")
//   MyYarns.shared.present(from: self)

import Foundation
import SwiftUI
import Combine

public struct ChatMessage: Identifiable, Equatable {
    public let id: String; public let text: String; public let sender: String; public let timestamp: Date
    public var isVisitor: Bool { sender == "visitor" }
    init(id: String = UUID().uuidString, text: String, sender: String, timestamp: Date = Date()) { self.id=id; self.text=text; self.sender=sender; self.timestamp=timestamp }
}

public struct WidgetConfig {
    public var name: String = "Support"; public var color: Color = Color(hex:"#25d16e") ?? .green
    public var uiColor: UIColor = UIColor(hex:"#25d16e") ?? .systemGreen; public var greeting: String = "Hi! How can we help?"; public var aiEnabled: Bool = true
}

@MainActor
public final class MyYarns: NSObject, ObservableObject, URLSessionWebSocketDelegate {
    public static let shared = MyYarns()
    private let baseURL = "https://myyarns.sendbaba.com"; private let wsURL = "wss://myyarns.sendbaba.com"
    @Published public var messages: [ChatMessage] = []; @Published public var config = WidgetConfig()
    @Published public var isConnected = false; @Published public var isTyping = false
    @Published public var isOpen = false; @Published public var unreadCount = 0
    private var widgetKey = ""; private var visitorName: String?; private var visitorEmail: String?; private var visitorPhone: String?
    private var webSocketTask: URLSessionWebSocketTask?; private var session: URLSession!
    private var typingTask: Task<Void,Never>?; private var reconnectTask: Task<Void,Never>?
    private let sessionId = "ios_\(Int(Date().timeIntervalSince1970))_\(Int.random(in:10000...99999))"
    private override init() { super.init(); session = URLSession(configuration:.default, delegate:self, delegateQueue:nil) }

    public func configure(widgetKey: String) { self.widgetKey=widgetKey; Task { await loadConfig(); await loadHistory(); connect() } }
    public func setVisitor(name: String?=nil, email: String?=nil, phone: String?=nil) { visitorName=name; visitorEmail=email; visitorPhone=phone; if isConnected { sendVisitorInfo() } }
    public func present(from vc: UIViewController) { let c=MyYarnsChatViewController(); let n=UINavigationController(rootViewController:c); if let s=n.sheetPresentationController{s.detents=[.large()]}; vc.present(n,animated:true); unreadCount=0 }
    public func open() { isOpen=true; unreadCount=0 }; public func close() { isOpen=false }
    public func sendMessage(_ text: String) { let t=text.trimmingCharacters(in:.whitespacesAndNewlines); guard !t.isEmpty else{return}; messages.append(ChatMessage(text:t,sender:"visitor")); send(["type":"message","content":t]) }

    private func loadConfig() async { guard let url=URL(string:"\(baseURL)/api/widgets/key/\(widgetKey)") else{return}; guard let(data,_)=try? await URLSession.shared.data(from:url) else{return}; guard let j=try? JSONSerialization.jsonObject(with:data) as? [String:Any],j["success"] as? Bool==true,let w=j["widget"] as? [String:Any] else{return}; config.name=w["name"] as? String ?? "Support"; config.greeting=w["greeting"] as? String ?? "Hi!"; if let hex=w["color"] as? String{config.color=Color(hex:hex)??(.green);config.uiColor=UIColor(hex:hex)??(.systemGreen)} }
    private func loadHistory() async { guard let url=URL(string:"\(baseURL)/api/conversations/session/\(sessionId)/messages") else{return}; guard let(data,_)=try? await URLSession.shared.data(from:url) else{return}; guard let j=try? JSONSerialization.jsonObject(with:data) as? [String:Any],j["success"] as? Bool==true,let arr=j["messages"] as? [[String:Any]] else{return}; messages=arr.compactMap{m in guard let t=m["content"] as? String else{return nil}; return ChatMessage(id:m["id"] as? String ?? UUID().uuidString,text:t,sender:m["sender_type"] as? String ?? "ai")} }
    func connect() { guard !widgetKey.isEmpty,let url=URL(string:"\(wsURL)/ws/\(widgetKey)/\(sessionId)") else{return}; webSocketTask=session.webSocketTask(with:url); webSocketTask?.resume(); listen() }
    private func listen() { webSocketTask?.receive{[weak self] result in guard let self else{return}; switch result{case .failure:Task{@MainActor in self.isConnected=false;self.scheduleReconnect()}; case .success(let m):if case .string(let t)=m{Task{@MainActor in self.handleMessage(t)}}; self.listen()} } }
    private func handleMessage(_ raw: String) { guard let data=raw.data(using:.utf8),let j=try? JSONSerialization.jsonObject(with:data) as? [String:Any],let type=j["type"] as? String else{return}; switch type{case "message":messages.append(ChatMessage(id:j["id"] as? String ?? UUID().uuidString,text:j["content"] as? String ?? "",sender:j["sender"] as? String ?? "ai")); if !isOpen{unreadCount+=1}; typingTask?.cancel(); isTyping=false; case "typing":isTyping=true; typingTask?.cancel(); typingTask=Task{[weak self] in try? await Task.sleep(nanoseconds:3_000_000_000); await MainActor.run{self?.isTyping=false}}; default:break} }
    private func sendVisitorInfo() { var p:[String:Any]=["type":"visitor_info","page":"ios-app","device":"mobile","os":"iOS"]; if let n=visitorName{p["name"]=n}; if let e=visitorEmail{p["email"]=e}; if let ph=visitorPhone{p["phone"]=ph}; send(p) }
    private func send(_ p:[String:Any]) { guard let d=try? JSONSerialization.data(withJSONObject:p),let s=String(data:d,encoding:.utf8) else{return}; webSocketTask?.send(.string(s)){_ in} }
    private func scheduleReconnect() { reconnectTask?.cancel(); reconnectTask=Task{[weak self] in try? await Task.sleep(nanoseconds:3_000_000_000); await MainActor.run{self?.connect()}} }
    nonisolated public func urlSession(_ session:URLSession,webSocketTask:URLSessionWebSocketTask,didOpenWithProtocol p:String?){Task{@MainActor[weak self]in self?.isConnected=true;self?.sendVisitorInfo()}}
    nonisolated public func urlSession(_ session:URLSession,webSocketTask:URLSessionWebSocketTask,didCloseWith c:URLSessionWebSocketTask.CloseCode,reason:Data?){Task{@MainActor[weak self]in self?.isConnected=false;self?.scheduleReconnect()}}
}

public struct MyYarnsLauncherView: View {
    let widgetKey:String; var visitorName:String?=nil; var visitorEmail:String?=nil; var visitorPhone:String?=nil
    @StateObject private var sdk = MyYarns.shared
    public init(widgetKey:String,visitorName:String?=nil,visitorEmail:String?=nil,visitorPhone:String?=nil){self.widgetKey=widgetKey;self.visitorName=visitorName;self.visitorEmail=visitorEmail;self.visitorPhone=visitorPhone}
    public var body: some View {
        ZStack(alignment:.bottomTrailing){Color.clear
            Button{sdk.isOpen ? sdk.close() : sdk.open()} label:{ZStack(alignment:.topTrailing){Circle().fill(sdk.config.color).frame(width:58,height:58).shadow(color:sdk.config.color.opacity(0.4),radius:12,y:6);Text(sdk.isOpen ? "✕" : "💬").font(.system(size:24)).frame(width:58,height:58);if sdk.unreadCount>0 && !sdk.isOpen{Text(sdk.unreadCount>9 ? "9+" : "\(sdk.unreadCount)").font(.system(size:9,weight:.black)).foregroundColor(.white).padding(4).background(Color.red).clipShape(Circle()).overlay(Circle().stroke(Color.white,lineWidth:2)).offset(x:4,y:-4)}}}.padding(24)
        }
        .sheet(isPresented:$sdk.isOpen){MyYarnsChatView().environmentObject(sdk)}
        .onAppear{sdk.configure(widgetKey:widgetKey);if let n=visitorName{sdk.setVisitor(name:n)};if let e=visitorEmail{sdk.setVisitor(email:e)}}
    }
}

struct MyYarnsChatView: View {
    @EnvironmentObject var sdk: MyYarns; @State private var inputText = ""; @FocusState private var focused: Bool
    var body: some View {
        VStack(spacing:0){
            HStack(spacing:12){ZStack{Circle().fill(Color.white.opacity(0.2)).frame(width:40,height:40);Text("💬").font(.system(size:20))};VStack(alignment:.leading,spacing:3){Text(sdk.config.name).font(.system(size:16,weight:.bold)).foregroundColor(.white);HStack(spacing:5){Circle().fill(sdk.isConnected ? Color(hex:"#4ADE80") ?? .green : Color(hex:"#FBBF24") ?? .yellow).frame(width:7,height:7);Text(sdk.isConnected ? "Online" : "Reconnecting...").font(.system(size:12)).foregroundColor(.white.opacity(0.85))}};Spacer();Button{sdk.close()} label:{ZStack{Circle().fill(Color.white.opacity(0.2)).frame(width:32,height:32);Text("✕").font(.system(size:14,weight:.bold)).foregroundColor(.white)}}}
            .padding(.horizontal,16).padding(.vertical,14).background(sdk.config.color)
            ScrollViewReader{proxy in ScrollView{LazyVStack(spacing:10){if sdk.messages.isEmpty{VStack(spacing:12){Text("👋").font(.system(size:40));Text(sdk.config.greeting).font(.system(size:15)).foregroundColor(.gray).multilineTextAlignment(.center)}.frame(maxWidth:.infinity).padding(.top,60)}else{ForEach(sdk.messages){m in MsgRow(message:m,accentColor:sdk.config.color)}};if sdk.isTyping{HStack(alignment:.bottom,spacing:8){Circle().fill(sdk.config.color).frame(width:28,height:28).overlay(Text("AI").font(.system(size:9,weight:.black)).foregroundColor(.white));Text("● ● ●").font(.system(size:12)).foregroundColor(.gray).padding(.horizontal,14).padding(.vertical,12).background(Color.white).cornerRadius(16).shadow(color:.black.opacity(0.06),radius:4,y:1);Spacer()}.padding(.horizontal,16)};Color.clear.frame(height:1).id("btm")}.padding(16)}.background(Color(UIColor.systemGray6)).onChange(of:sdk.messages.count){_ in withAnimation{proxy.scrollTo("btm")}}.onChange(of:sdk.isTyping){_ in withAnimation{proxy.scrollTo("btm")}}}
            VStack(spacing:0){Divider();HStack(alignment:.bottom,spacing:8){TextField("Type a message...",text:$inputText,axis:.vertical).lineLimit(1...4).font(.system(size:14)).padding(.horizontal,16).padding(.vertical,10).background(Color(UIColor.systemGray6)).cornerRadius(22).overlay(RoundedRectangle(cornerRadius:22).stroke(Color(UIColor.separator),lineWidth:1)).focused($focused).onSubmit{sendMsg()};Button(action:sendMsg){ZStack{Circle().fill(inputText.trimmingCharacters(in:.whitespaces).isEmpty ? sdk.config.color.opacity(0.4) : sdk.config.color).frame(width:42,height:42);Text("↑").font(.system(size:18,weight:.black)).foregroundColor(.white)}}}.padding(12);Text("Powered by MyYarns · SendBaba").font(.system(size:10)).foregroundColor(Color(UIColor.tertiaryLabel)).padding(.bottom,8)}.background(Color.white)
        }
    }
    private func sendMsg(){let t=inputText.trimmingCharacters(in:.whitespacesAndNewlines);guard !t.isEmpty else{return};sdk.sendMessage(t);inputText=""}
}

struct MsgRow: View {
    let message: ChatMessage; let accentColor: Color
    var body: some View {
        HStack(alignment:.bottom,spacing:8){if message.isVisitor{Spacer(minLength:60)} else{Circle().fill(accentColor).frame(width:28,height:28).overlay(Text("AI").font(.system(size:9,weight:.black)).foregroundColor(.white))}
        VStack(alignment:message.isVisitor ? .trailing : .leading,spacing:4){Text(message.text).font(.system(size:14)).foregroundColor(message.isVisitor ? .white : Color(UIColor.label)).padding(.horizontal,14).padding(.vertical,10).background(message.isVisitor ? accentColor : Color.white).cornerRadius(16).shadow(color:message.isVisitor ? .clear : .black.opacity(0.06),radius:4,y:1);Text(message.timestamp,style:.time).font(.system(size:10)).foregroundColor(.gray)}
        if !message.isVisitor{Spacer(minLength:60)}}
    }
}

public class MyYarnsChatViewController: UIViewController {
    public override func viewDidLoad(){super.viewDidLoad();title=MyYarns.shared.config.name;let c=UIHostingController(rootView:MyYarnsChatView().environmentObject(MyYarns.shared));addChild(c);view.addSubview(c.view);c.view.translatesAutoresizingMaskIntoConstraints=false;NSLayoutConstraint.activate([c.view.topAnchor.constraint(equalTo:view.topAnchor),c.view.bottomAnchor.constraint(equalTo:view.bottomAnchor),c.view.leadingAnchor.constraint(equalTo:view.leadingAnchor),c.view.trailingAnchor.constraint(equalTo:view.trailingAnchor)]);c.didMove(toParent:self);MyYarns.shared.unreadCount=0}
}

extension Color { init?(hex:String){var h=hex.trimmingCharacters(in:.whitespacesAndNewlines).trimmingCharacters(in:CharacterSet(charactersIn:"#"));if h.count==3{h=h.map{"\($0)\($0)"}.joined()};guard h.count==6,let v=UInt64(h,radix:16) else{return nil};self.init(red:Double((v>>16)&0xFF)/255,green:Double((v>>8)&0xFF)/255,blue:Double(v&0xFF)/255)} }
extension UIColor { convenience init?(hex:String){var h=hex.trimmingCharacters(in:.whitespacesAndNewlines).trimmingCharacters(in:CharacterSet(charactersIn:"#"));if h.count==3{h=h.map{"\($0)\($0)"}.joined()};guard h.count==6,let v=UInt64(h,radix:16) else{return nil};self.init(red:CGFloat((v>>16)&0xFF)/255,green:CGFloat((v>>8)&0xFF)/255,blue:CGFloat(v&0xFF)/255,alpha:1)} }
