::requires 'ooSQLite.cls'
::requires "rxunixsys" LIBRARY
::requires 'ncurses.cls'
::requires 'app/utils.rex'

::CLASS AddressBookDB PUBLIC

    ::method init
      expose db

      /* Create database directory if it doesn't exist */
      if SysFileExists(.dbPath) == .false then DO
        SAY "Initializing new address book"
        db = .ooSQLiteConnection~new(.dbPath)
        self~createTables
      END
      Else Do
          Say "Initializing existing address book"
          db = .ooSQLiteConnection~new(.dbPath,.ooSQLite~OPEN_READWRITE)
      End
    return

    ::METHOD getFileName
      expose db
    return db~fileName()

    ::METHOD closeDb
      expose db
      db~Close()
    RETURN

    ::METHOD createTables
      expose db

      /* Contacts table */
      db~exec("CREATE TABLE IF NOT EXISTS contacts ("||,
                 "id INTEGER PRIMARY KEY AUTOINCREMENT,"||,
                 "first_name TEXT,"||,
                 "last_name TEXT,"||,
                 "created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,"||,
                 "updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP"||,
                 ")")

      /* Phone numbers table */
      db~exec("CREATE TABLE IF NOT EXISTS phone_numbers ("||,
                 "id INTEGER PRIMARY KEY AUTOINCREMENT,"||,
                 "contact_id INTEGER,"||,
                 "type TEXT,"||,  -- home, work, mobile, etc.
                 "number TEXT,"||,
                 "FOREIGN KEY (contact_id) REFERENCES contacts(id) ON DELETE CASCADE"||,
                 ")")

      /* Email addresses table */
      db~exec("CREATE TABLE IF NOT EXISTS email_addresses ("||,
                 "id INTEGER PRIMARY KEY AUTOINCREMENT,"||,
                 "contact_id INTEGER,"||,
                 "type TEXT,"||,  -- home, work, etc.
                 "email TEXT,"||,
                 "FOREIGN KEY (contact_id) REFERENCES contacts(id) ON DELETE CASCADE"||,
                 ")")

      /* Physical addresses table */
      db~exec("CREATE TABLE IF NOT EXISTS addresses ("||,
                 "id INTEGER PRIMARY KEY AUTOINCREMENT,"||,
                 "contact_id INTEGER,"||,
                 "type TEXT,"||,  -- home, work, etc.
                 "street TEXT,"||,
                 "city TEXT,"||,
                 "state TEXT,"||,
                 "postal_code TEXT,"||,
                 "country TEXT,"||,
                 "FOREIGN KEY (contact_id) REFERENCES contacts(id) ON DELETE CASCADE"||,
                 ")")
    return

    /* Contact CRUD Operations */
    ::METHOD addContact
      expose db
      use arg contactDict /* Use a Rexx 'directory' to pass multiple values around */
      sql = "INSERT INTO contacts (first_name, last_name) VALUES ('"contactDict~firstName"', '"contactDict~lastName"')"
      rc = db~exec(sql)
      if rc \= 0 then do
        say "Error adding contact:" db~errMsg()
        return -1
      end
      contactId = db~lastInsertRowId() /* the row id of 'contacts' table is the master id */
      self~addPhoneNumber(contactId, contactDict["PHONE_TYPE"], contactDict["PHONE_NUMBER"])
      self~addEmailAddress(contactId, contactDict["EMAIL_TYPE"], contactDict["EMAIL_ADDRESS"])
      self~addRealAddress(contactId, contactDict~addressType, contactDict~street, contactDict~city, contactDict~state,
                      contactDict~postalCode, contactDict~country)
    return contactId

    ::METHOD getContact
      expose db
      use arg contactId
      returnedContent = .Directory~new()
      sql1 = "SELECT * FROM contacts WHERE id = "contactId
      contacts = db~exec(sql1,.true,.ooSQLite~OO_ARRAY_OF_DIRECTORIES)
      /* The result will be rendered as an array of Rexx 'directories'
       * which are basically analogous to python dictionaries. */

      if contacts = .nil then DO
          Say 'NO CONTACTS FOUND'
          return
      END
      if contacts~size < 1 then do
          Say 'NO CONTACT FOUND'
          return
      END
      return contacts[1] /* Rexx is 1-indexed */
/***
      returnedContent~firstName = contact["FIRST_NAME"]
      returnedContent~lastName = contact["LAST_NAME"]
      sql2 = "SELECT * FROM email_addresses WHERE contact_id = "contactId
      emails = db~exec(sql2,.true,.ooSQLite~OO_ARRAY_OF_DIRECTORIES)
      email_array = .Array~new()
      do row over emails
        email_array.append(row["EMAIL"])
      END
      returnedContent~emailAddresses = email_array

      sql3 = "SELECT * FROM phone_numbers WHERE contact_id = "contactId
      phones = db~exec(sql3,.true,.ooSQLite~OO_ARRAY_OF_DIRECTORIES)
      phone_dir = .Directory~new()
      phone_array = .Array~new()
      do phone over phones
        phone_dir~put(phone["NUMBER"], phone["TYPE"])
        phone_array.append(phone_dir)
      END
      returnedContent~phoneNumbers = phone_array

      sql4 = "SELECT * FROM addresses WHERE contact_id = "contactId
      addresses = db~exec(sql3,.true,.ooSQLite~~OO_ARRAY_OF_DIRECTORIES)
      address_dir = .Dictionary~new()
      address_array = .Array~new()
      do address over addresses
        address_dir~type = address["TYPE"]
        address_dir~street = address["STREET"]
        address_dir~city = address["CITY"]
        address_dir~state = address["STATE"]
        address_dir~postal_code = address["POSTAL_CODE"]
        address_dir~country = address["COUNTRY"]
        address_array.append(address_dir)
      END
      returnedContent~addresses = address_array
    return returnedContent
***/

    ::METHOD getAllContacts
        expose db
        sql = "SELECT * FROM contacts ORDER BY last_name, first_name"
        contacts = db~exec(sql, .true, .ooSQLite~OO_ARRAY_OF_DIRECTORIES)
    return contacts

    ::METHOD searchContacts
      expose db
      use arg searchTerm

      contactsList = .Array~new()

      sql = "SELECT id FROM contacts WHERE first_name LIKE '%"searchTerm"%' OR last_name LIKE '%"searchTerm"%' ORDER BY last_name, first_name"
      contactIds = db~exec(sql, .true, .ooSQLite~OO_ARRAY_OF_DIRECTORIES)

      do contactDir over contactIds
        contactId = contactDir["ID"]
        contact = self~getContact(contactId)
        if contact \= .nil then do
          contactsList~append(contact)
        end
      end
    return contactsList

    ::METHOD updateContact
        expose db
        use arg contactId, firstName, lastName
        sql = "UPDATE contacts SET first_name = '"firstName"', last_name = '"lastName"' WHERE id = "contactId
        rc = db~exec(sql)
        if rc \= 0 then do
          say "Error updating contact:" db~errMsg()
          return -1
        end
        return rc
/**
        self~removeContactPhones(contactId)
        self~removeContactEmails(contactId)
        self~removeContactAddresses(contactId)

        /* Then add new entries */
        self~addPhoneNumber(contactId, contactDict["PHONE_TYPE"], contactDict["PHONE_NUMBER"])
        self~addEmailAddress(contactId, contactDict["EMAIL_TYPE"], contactDict["EMAIL_ADDRESS"])
        self~addAddress(contactId, contactDict~addressType, contactDict~street, contactDict~city, contactDict~state,
                       contactDict~postalCode, contactDict~country)
    return 0
**/

    ::METHOD deleteContact
      expose db
      use arg contactId

      /* Delete all related records first (assuming foreign key constraints) */
      self~removeContactPhones(contactId)
      self~removeContactEmails(contactId)
      self~removeContactAddresses(contactId)

      /* Delete the contact record */
      sql = "DELETE FROM contacts WHERE id = "contactId
      rc = db~exec(sql)

      if rc \= 0 then do
        say "Error deleting contact:" db~errMsg()
        return -1
      end
    return 0

    ::METHOD getPhoneNumbers
      expose db
      use arg contactId

      phones = .Array~new
      sql = "SELECT id, type, number FROM phone_numbers WHERE contact_id = "contactId
      phones = db~exec(sql,.true, .ooSQLite~OO_ARRAY_OF_DIRECTORIES)

      return phones

    ::METHOD addPhoneNumber
      expose db
      use arg contactId, phoneType, phoneNumber

      if phoneType = .nil | phoneNumber = .nil then return 0

      sql = "INSERT INTO phone_numbers (contact_id, type, number) VALUES ("contactId", '"phoneType"', '"phoneNumber"')"
      rc = db~exec(sql)
      if rc \= 0 then DO
        Say "Unable to insert phone number for contact id "contactId
        return rc
      END
      phone_id = db~lastInsertRowId()
    return phone_id

    ::METHOD updatePhoneNumber
      expose db
      use arg phoneId, phoneType, phoneNumber

      sql = "UPDATE phone_numbers SET type = '"phoneType"', number = '"phoneNumber"' WHERE id = "phoneId
      rc = db~exec(sql)
      if rc \= 0 then DO
        Say "Unable to update phone number or type for phone id "contactId
        return rc
      END

      return rc

    ::METHOD deletePhoneNumber
      expose db
      use arg phoneId

      stmt = db~prepare("DELETE FROM phone_numbers WHERE id = ?")
      stmt~bind(1, phoneId)
      stmt~step

    return db~changes() > 0



    /* Email address operations */

    ::METHOD addEmailAddress
      expose db
      use arg contactId, emailType, emailAddress

      if emailType = .nil | emailAddress = .nil then return 0

      sql = "INSERT INTO email_addresses (contact_id, type, email) VALUES ("contactId", '"emailType"', '"emailAddress"')"
      rc = db~exec(sql)
      if rc \= 0 then do
        say "Error adding email address:" db~errMsg()
        return -1
      end

      email_id = db~lastInsertRowId()

    return email_id

    ::METHOD getEmailAddresses
      expose db
      use arg contactId

      sql = "SELECT id, type, email FROM email_addresses WHERE contact_id = "contactId
      emails = db~exec(sql, .true, .ooSQLite~OO_ARRAY_OF_DIRECTORIES)
      return emails

    ::METHOD updateEmailAddress
      expose db
      use arg emailId, emailType, emailAddress

      sql = "UPDATE email_addresses SET type = '"emailType"', email = '"emailaddress"' WHERE id = "emailId
      rc = db~exec(sql)
      if rc \= 0 then do
        say "Error adding email address:" db~errMsg()
        return -1
      end
      return rc


    ::METHOD deleteEmailAddress
      expose db
      use arg emailId

      stmt = db~prepare("DELETE FROM email_addresses WHERE id = ?")
      stmt~bind(1, emailId)
      stmt~step

      return db~changes() > 0

    /* Physical address operations */

    ::METHOD addRealAddress
      expose db
      use arg contactId, addressType, street, city, state, postalCode, country

      if addressType = .nil | street = .nil then return 0

      sql = "INSERT INTO addresses (contact_id, type, street, city, state, postal_code, country)",
            "VALUES ("contactId", '"addressType"', '"street"', '"city"', '"state"', '"postalCode"', '"country"')"
      rc = db~exec(sql)

      if rc \= 0 then do
        say "Error adding address:" db~errMsg()
        return -1
      end
      addr_id = db~lastInsertRowId()
    return addr_id

    ::METHOD getRealAddresses
      expose db
      use arg contactId

      sql = "SELECT id, type, street, city, state, postal_code, country" || ,
                       " FROM addresses WHERE contact_id = " contactId
      addresses = db~exec(sql, .true, .ooSQLite~OO_ARRAY_OF_DIRECTORIES)
      return addresses

    ::METHOD updateRealAddress
      expose db
      use arg addressId, addressType, street, city, state, postalCode, country

      sql = "UPDATE addresses SET type = '"addressType"', street = '"street"', city = '"city"'," || ,
            " state = '"state"', postal_code = '"postalCode"', country = '"country"' WHERE id = "addressId
      rc = db~exec(sql)

      if rc \= 0 then do
          say "Error updating address:" db~errMsg()
          return -1
      end

      return rc

    ::METHOD deleteAddress
      expose db
      use arg addressId

      sql="DELETE FROM addresses WHERE id = "addressId
      rc = db~exec(sql)

      if rc \= 0 then do
        say "Error removing address:" db~errMsg()
        return -1
      end

      return 0

    ::METHOD removeContactPhones
      expose db
      use arg contactId

      sql = "DELETE FROM phone_numbers WHERE contact_id = "contactId
      rc = db~exec(sql)

      if rc \= 0 then do
        say "Error removing phone numbers:" db~errMsg()
        return -1
      end

      return 0

    ::METHOD removeContactEmails
      expose db
      use arg contactId

      sql = "DELETE FROM email_addresses WHERE contact_id = "contactId
      rc = db~exec(sql)

      if rc \= 0 then do
        say "Error removing email addresses:" db~errMsg()
        return -1
      end
    return 0

    ::METHOD removeContactAddresses
      expose db
      use arg contactId

      sql = "DELETE FROM addresses WHERE contact_id = "contactId
      rc = db~exec(sql)

      if rc \= 0 then do
        say "Error removing addresses:" db~errMsg()
        return -1
      end
    return 0

